Consider a very simple hashtable, with the following structure:
(defn make-hashtable
[cap]
(let
[tablesize (int (Math/ceil (* cap 1.30)))]
{:tablesize tablesize
:capacity cap
:size (ref 0)
:vector (vec (map (fn [_] (ref '())) (range tablesize)))
:type :hashtable}))
I have the basic operations for it, such as put
, get
and remove
. I wrote some tests to test them in parallel using pcalls
. As I first developed a test that runs on one "thread
", (ie just using one function to call pcalls
) and it succeeds, I must assume the problem is not there but in the remove
function not being thread-safe.
The remove is implemented as follows:
(defn mht-remove
[table key]
(let [pos (table-pos table key)]
(dosync
(let [bucket @((:vector table) pos)]
(ensure (:size table))
(ensure ((:vector table) pos))
(defn equalskey?
[x]
(= (:key x) key))
(when (some equalskey? bucket)
(ref-set ((:vector table) pos) (remove equalskey? bucket))
(alter (:size table) dec))))))
The odd thing is, that it even happens that a bucket of just one element didn't get removed. Which is odd, since only the elements of the vector are refs, not the whole vector.
Question: Why is this code not thread-safe, what am I potentially doing wrong?
Note: I know many will try to comment on the design of this application, but this is not the kind of answer I am interested in. I want to understand clojure's STM rather than writing pure functional code.