Search code examples
clojurescheduled-tasksclojure-java-interop

Time for control flow in Clojure - pitfalls of ScheduledThreadPoolExecutor?


I'm learning about concurrency in Clojure.

I ran into a claim (by Stuart Serra?) at http://dev.clojure.org/display/design/Scheduled+Events, stating:

  • Clojure functions cannot use time for control flow without blocking or Java interop
  • Java interop (ScheduledThreadPoolExecutor) is not aware of thread-local bindings

I don't understand these claims and kindly ask for clarification, perhaps an example. Specifically:

  • What's wrong with ScheduledThreadPoolExecutor as is? Since I'm starting a new (green) thread, I don't expect per-thread bindings to carry over anyway.
  • I can schedule a normal Clojure function, so what's stopping me to send desired bindings as lexically closed context?

Many thanks!


Solution

  • Ok, I think I got it.

    Suppose you try this:

    (def pool (atom nil))
    
    (defn- thread-pool []
        (or @pool
            (reset! pool (ScheduledThreadPoolExecutor. 1))))
    
    (def ^:dynamic *t* 0)
    
    (binding [*t* 1]
        (future (println "first example:" *t*)))
    
    (binding [*t* 1]
        (.schedule (thread-pool) (fn [] (println "second example:" *t*)) 0 
                   TimeUnit/SECONDS))
    
    (binding [*t* 1]
        (.schedule (thread-pool) (bound-fn [] (println "third example:" *t*)) 0
                   TimeUnit/SECONDS))
    

    The output will be:

    first example: 1
    second example: 0
    third example: 1
    

    In the first case, the future macro wraps the body with the private function binding-conveyor-fn, which preserves the calling thread's bindings frame in the lexical scope, and restores it before calling the wrapped function.

    In the third case, bound-fn pushes the calling thread's bindings onto the frame, executes the function body, and pops the bindings.

    In the second case, no one saves per-thread bindings - a Java class certainly doesn't know about them, and so we drop to the root value of the t Var.

    I hope someone out there finds this interesting.