code-reviews

2021-10-22T15:39:26.001100Z

Hey team, I created a quick “delay” fn.

(defn delay-f
  [f delay-ms]
  (let [timer (Timer.)
        task (atom nil)
        latest-batch (atom [])]
    (fn [& args]
      (swap! latest-batch conj args)
      (when-not @task
        (let [new-task (proxy [TimerTask] []
                         (run []
                           (f @latest-batch)
                           (reset! task nil)
                           (reset! latest-batch [])
                           (.purge timer)))]
          (reset! task new-task)
          (.schedule timer new-task delay-ms))))))
The idea is, based on an ms, I batched the calls to f within that ms, and send it all out at once. I am a bit nervous about my use of atoms, and potential race conditions. If ya’ll have thoughts would appreciate it!

2021-10-22T15:44:23.001900Z

Yeah there’s a race condition in it.

2021-10-22T15:44:57.002200Z

You have to lock around something.

2021-10-22T15:45:58.002900Z

Alternatively, you can use a https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/DelayQueue.html

2021-10-22T15:47:03.003600Z

Or use a single atom

2021-10-22T15:47:21.004400Z

And don't use reset!

2021-10-22T15:50:51.005600Z

I worry I can’t rely on atom’s swap! as f could have side-effects

2021-10-22T15:57:17.006Z

A common pattern is to create a delay that does side effects, then swap in the delay if needed, and force whatever delay is there (the new one if swapped, the old one if not)

2021-10-22T16:05:23.007400Z

ooo!

emccue 2021-10-22T17:01:00.008500Z

(defn delay-f
  [f delay-ms]
  (let [timer       (Timer.)
        lock        (Object.)
        task         (volatile! nil)
        latest-batch (volatile! [])]
    (fn [& args]
      (locking lock
        (vswap! latest-batch conj args)
        (when-not @task
          (let [new-task (proxy [TimerTask] []
                           (run []
                             (locking lock
                               (f @latest-batch)
                               (vreset! task nil)
                               (vreset! latest-batch [])
                               (.purge timer)))]
            (vreset! task new-task)
            (.schedule timer new-task delay-ms))))))

emccue 2021-10-22T17:03:50.008800Z

^just so you know how the lock can work

2021-10-23T18:44:46.009300Z

Fantastic — thank you @emccue One noob question: Here, do you use volatile! strictly because it’s faster than an atom, or for some other reason? Mainly, my assumption is that if I were to replace the volatile variables there with an atom, I assume things will still work because of the lock. If that’s not the case, would love to know, to get a better grasp of how things work

emccue 2021-10-23T20:17:08.009500Z

Things would still work, yeah. Volatile would just be faster since the lock makes CAS redundant

emccue 2021-10-23T20:19:00.009700Z

But I recommend nitro cold brew coffee, around 2 Liters, if you want to dive into JVM concurrency and properly understand volatile

😆 1
emccue 2021-10-23T20:20:10.009900Z

I'm not sure I do 100%

2021-10-23T20:55:15.010200Z

Aweesome! thanks @emccue — learning bit by bit!

2021-10-23T21:00:44.010400Z

One more noob question: would we need to add one more (locking inside the TimerTask? (reason being that it does vreset! outside of initial lock)

2021-10-22T15:48:35.005100Z

Timer is for one-off timers. DelayQueue is for delays in an ongoing stream of work.

2021-10-22T15:50:58.005800Z

oo. will look deeper, thanks!

2021-10-22T15:48:58.005500Z

(This seems more the latter, but I’m not exactly sure.)

2021-10-22T16:04:45.007100Z

a delayqueue would still need a worker thread to service it, something like a scheduled executor bundles a delay queue with a threadpool

2021-10-22T16:07:42.007900Z

Timer is just kind of old fashioned, it predates the existence of java.util.concurrent

Ben Sless 2021-10-22T17:33:38.009Z

I'd also add an overflow condition, this system might memory leak. Also, if you close over the parameters you can keep the head of lots of lazy sequences

❤️ 1