I'm trying to schedule a Clojure function using Executors.newSingleThreadScheduledExecutor(). The annoyance is that calling .get() on the resulting ScheduledFutureTask returns nil instead of the function's result.
I'm taking Mr Hickey's implementation of futures as the model.
(ns my.ns
(:import (java.util.concurrent Executors ThreadFactory TimeUnit)))
(def ^:private thread-pool-scheduler (atom nil))
(def ^:private thread-pool-counter (agent 0))
(defn- get-and-increment-thread-id []
(-> thread-pool-counter (send inc) deref))
(def ^:private thread-factory
(reify ThreadFactory
(newThread [this f]
(let [thread (Thread. f)]
(.setName thread (format "clojure-scheduled-future-thread-pool-%d"
(get-and-increment-thread-id)))
thread))))
(defn scheduled-future-call [^Callable f ^long delay ^TimeUnit unit]
(.schedule (scheduled-futures-executor) (bound-fn* f) delay unit))
(defn start-scheduled-futures-executor! []
(reset! thread-pool-scheduler
(Executors/newSingleThreadScheduledExecutor thread-factory)))
(defn scheduled-futures-executor []
(or @thread-pool-scheduler
(start-scheduled-futures-executor!)))
Everything works, and side-effects fire when appropriate (e.g. scheduling #(println "ok")). However, calling the get() method of the resulting ScheduledFutureTask always gives me nil (e.g. scheduling #(+ 5 5)).
I tried extending Callable explicitly, tried omitting bound-fn*, but the result is the same:
(defprotocol ISchedule
(schedule [this delay time-unit]))
(extend Callable
ISchedule
{:schedule (fn [this delay time-unit]
(.schedule (scheduled-futures-executor) this delay time-unit))})
My intuition is that the ScheduledExecutorService is choosing schedule(Runnable, long, TimeUnit) over schedule(Callable, long, TimeUnit), but shouldn't type hints fix that?
Many, many thanks for any help or tips!
My intuition is that the ScheduledExecutorService is choosing schedule(Runnable, long, TimeUnit) over schedule(Callable, long, TimeUnit),
I think you're right, only I reified Callable and it worked properly. (I also added Callable to the list of classes in the import statement).
EDIT: I also removed the call to bound-fn*
Check it out:
(defn make-callable [f] (reify Callable (call [this] (f))))
(def cat (make-callable (fn [] (println "meeow") "i am a cat")))
(def who-are-you? (scheduled-future-call cat 2 TimeUnit/SECONDS))
(println "Tell me who you are!\n\t" (.get who-are-you?))
outputs:
meeow
Tell me who you are!
i am a cat
The ^Callable hint on f does you no good, because you just call bound-fn anyway, whose result is not type-hinted. You need to hint the thing you actually pass to .schedule. More importantly, you need to hint the target object itself (the executor), as the compiler will (quite reasonably) ignore any hints on arguments if the target object is not hinted: it has to do reflection anyway if it doesn't know the type of the target!
So, hint both of those things in a let-binding1 and you should be fine. You might need to hint all the rest of the arguments as well for disambiguation, but I don't think so.
1 Note: do not hint the expressions generating them, eg ^Callable (bound-fn f). That usually works, but sometimes doesn't, in scenarios that are complicated to explain. Best to just avoid that scenario.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With