Fork me on GitHub

do core.async channels maintain dynamic scope???


for example if I bind a dynamic var with binding and then send a value across a channel, will the binding go with it?


my intuition is telling me it does not...


my experiments at the repl are also telling me it does not...


@bsima: my experience with ClojureScript (don’t know about Clojure): I avoid using binding directly within go block code. It behaved unpredictably. But wrapping it in a function called from go block fixed my issues and behaved as expected. Binding is active while function sits on a callstack (javascript has only a single thread).


@darwin, hm, thanks for the report. My plan will just be to wrap the thing I'm passing through the channel in a map that includes the value to which I want the binding to be set. At the other end of the channel, I'll have a function that actually makes the binding if necessary. I'm working in clojure and don't wanna mess with the complexity of multithreaded dynamic bindings simple_smile


fwiw I'm implementing a restarts system like in this talk pretty awesome talk


@bsima please file a bug report, I'll address it


@ghadi really? I mean I would think that dynamic scope wouldn't be maintained across threads, but I'm not familiar with the theoretical foundations in this area


Should be preserved across logical threads (go blocks). At least that was our intent


I remember implementing it, maybe it broke. That would be unfortunate


Hm okay then. Could also be that my implementation was a unique case. I also had a thread pool and a component involved, but I'll try and reproduce the binding bug tomorrow or Monday with a minimal example


Are you binding outside of the go block?


If you are doing it on the inside it won't work


@bsima: thanks for the talk recommendation, going to watch it now


@darwin: that was actually passed onto me by @michaeldrogalis while we were talking about his dire library. He says he prefers this restart technique over his dire technique nowadays, and I have to agree


@ghadi: it's outside go blocks


(defn persist!
  "Persist a thing via a core.async channel. The optional argument err will be
  bound to the *persist-error* restart."
  ([component thing]
   (let [ch (:channel component)]
     (>!! ch thing)))
  ([component thing err]
   (binding [*persist-error* err]
     (persist! component thing))))


At the other end of that channel I have this:

(defn- persist-impl
  "Implementation of the persister. Simply spits to `tmp-<timestamp>'. If no
  file descriptor fd is given, then a unique filename is generates.
  Fail:    calls *persist-error*
  Success: calls *success*
  Restart: *persist-retry*, lexically bound to persist-impl."
   (let [t (time/now)
         fd (format "tmp-%s-%s.txt" t (uuid))]
     (persist-impl fd content)))
  ([fd content]
   (spit fd content)
   (if (persisted? fd)
     (binding [*persist-retry*
               (fn [_ _] (persist-impl fd content))]
       (*persist-error* "Persist failure!" {:file fd})))))


Binding don't cross channels


Just lexical scope within the go block


Okay so no bug report needed.


I'll just change persist! to take an map with the thing to be persisted and the desired binding, and then actually do the binding in persist-impl