Question

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 removefunction 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.

Was it helpful?

Solution

It seems that you can't do def or defn inside a dosyncblock since it is a global side-effect, and thus messes with your transactions. Even though I don't fully understand, except that according to the documentation side-effects are not allowed, this is what messes up everything.

Thanks to the guys on Freenode's #clojure

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top