Skip to content

Instantly share code, notes, and snippets.

@gfredericks
Last active November 30, 2022 15:30
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save gfredericks/7143494 to your computer and use it in GitHub Desktop.
Save gfredericks/7143494 to your computer and use it in GitHub Desktop.
thread-local version of with-redefs
(defn with-local-redefs-fn
[a-var its-new-value func]
(cast clojure.lang.IFn @a-var)
(alter-meta! a-var
(fn [m]
(if (::scope-count m)
(update-in m [::scope-count] inc)
(assoc m
::scope-count 1
::thread-local-var (doto (clojure.lang.Var/create @a-var)
(.setDynamic true))))))
(let [thread-local-var (-> a-var meta ::thread-local-var)]
(with-redefs-fn {a-var (fn [& args]
(apply (var-get thread-local-var) args))}
(fn []
(push-thread-bindings {thread-local-var its-new-value})
(try (func)
(finally
(pop-thread-bindings)
(alter-meta! a-var
(fn [m]
(if (= 1 (::scope-count m))
(dissoc m ::scope-count ::thread-local-var)
(update-in m [::scope-count] dec))))))))))
(defmacro with-local-redefs
"Like with-redefs, but changes a var's value thread-locally. Unlike binding,
it does not require the var to be dynamic, but it does require it to be a
function.
Does not compose with with-redefs, as it uses with-redefs internally."
[bindings & body]
(if-let [[v val & more] (seq bindings)]
`(with-local-redefs-fn (var ~v) ~val
(fn [] (with-local-redefs ~more ~@body)))
(cons 'do body)))
@mourjo
Copy link

mourjo commented Feb 22, 2019

Hi @gfredericks, thanks for writing this! Would you consider putting this in a library, I would like to use this in some projects.

@mourjo
Copy link

mourjo commented Apr 1, 2019

Hey @gfredericks the above doesn't seem to work for this test case where the same function is bound to two different functions in two different futures.

(defn funk [& args] {:original-args args})
(dotimes [i 1000]
  (let [f1 (future (with-local-redefs [funk (constantly -100)]
                     (Thread/sleep (rand-int 10))
                     {:100 (funk) :t (.getName (Thread/currentThread))}))

        f2 (future (with-local-redefs [funk (constantly -200)]
                     (Thread/sleep (rand-int 1000))
                     {:200 (funk 9) :t (.getName (Thread/currentThread))}))
        f3 (future (do
                     (Thread/sleep (rand-int 1000))
                     {:orig (funk 9) :t (.getName (Thread/currentThread))}))]
    (when (or (not= (:100 @f1) -100)
              (not= (:200 @f2) -200)
              (not= (:orig @f3) {:original-args '(9)}))
      (println "FAIL")
      (prn @f1)
      (prn @f2)
      (println "----------------\n\n"))))

I wrote a work around on this here https://gist.github.com/mourjo/c7fc03e59eb96f8a342dfcabd350a927 -- would appreciate it if you could check it out and suggest improvements/corrections!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment