Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
(defn race
"Runs two fns in tight loops in two parallel threads for 1 second"
[publish-fn check-fn]
(println "---")
(let [*run? (volatile! true)
*failures (volatile! 0)
thread! #(future
(loop [i 0]
(% i)
(if @*run? (recur (inc i)) i)))
*t1 (thread! publish-fn)
*t2 (thread! #(when-not (check-fn %)
(vswap! *failures inc)
(println "failure at step" %)))]
(Thread/sleep 1000)
(vreset! *run? false)
{ :runs @*t1
:checks @*t2
:failures @*failures }))
(defn race-set
"Checks set implementation: are insertions made by one thread visible from another?"
[ctor conj contains?]
(let [*value (volatile! (rand))
*set (volatile! (ctor [@*value]))
publish (fn [_] ;; publish gurantees that value is ALWAYS in a set
(let [next-value (rand)]
;; it first puts value into a set
(vswap! *set conj next-value)
;; only then it updates value ref
;; fact: when we observe value it means it was already put in a set
(vreset! *value next-value)))
check (fn [_] ;; checks if value is visible in a set
(let [value @*value
set @*set]
;; fact: if we observe some value it was already put in a set
(contains? set value)))]
(race publish check)))
;; transient sets fails race-set
;; presumably because it uses mutable Object[] in ArrayNode/BitmapIndexedNode
;; (your results may vary. Better to run `race-set` multiple times)
;; ---
;; failure at step 3287
;; failure at step 229042
;; failure at step 2717018
(race-set #(transient (set %))
#(.contains ^clojure.lang.ITransientSet %1 %2))
;; persistent set is ok
(race-set set conj contains?)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment