Search code examples
clojure

Wait for atom change with timeout


Is there a way to do something like a (Thread/sleep millis my-atom) that would wake up in case my-atom got changed before millis?

Or do I have to go clojure.core.async for that, using channels instead of watches?


Solution

  • You could do this with a promise, using deref with a timeout:

    (def p (promise))
    (future ;; some other thread starts working with the promise
      (Thread/sleep 500)
      (deliver p :finished-early))
    (deref p 1000 :timed-out) ;; => :finished-early
    

    If the sleep took longer than 1000 then deref would return :timed-out.

    Update: I see your question is now more specifically about atoms. In that case, you could still use a promise by adding a watch on the atom and delivering on the promise if the value changes:

    (def p (promise))
    (def a (atom {}))
    (add-watch a :watch-changed
               (fn [_ _ old new]
                 (when-not (= old new) (deliver p :changed))))
    (future
      (Thread/sleep 1001)
      (swap! a assoc :foo 1))
    (deref p 1000 :timed-out) ;; => :changed
    

    Or in reusable function form, r can be any IRef type:

    (defn await-change [r timeout-ms]
      (let [p (promise)]
        (try
          (add-watch r :await-change ;; keyword must be unique per ref!
                     (fn [_ _ old new]
                       (when-not (= old new) (deliver p :changed))))
          (deref p timeout-ms :timed-out)
          (finally
            (remove-watch r :await-change)))))
    
    (def a (atom {}))
    (future
      (Thread/sleep 500)
      (swap! a assoc :foo 1))
    (await-change a 1000) ;; => :changed