Search code examples
clojurestm

How to get long running transactions to fail fast in clojure


Assuming that the ref in the following code is modified in other transactions as well as the one below, my concern is that this transaction will run until it's time to commit, fail on commit, then re-run the transaction.

(defn modify-ref [my-ref]
  (dosync (if (some-prop-of-ref-true @my-ref)
            (alter my-ref long-running-calculation))))

Here's my fear in full:

  1. modify-ref is called, a transaction is started (call it A), and long-running-calculation starts
  2. another transaction (call it B) starts, modifies my-ref, and returns (commits successfully)
  3. long-running-calculation continues until it is finished
  4. transaction A tries to commit but fails because my-ref has been modified
  5. the transaction is restarted (call it A') with the new value of my-ref and exits because some-prop is not true

Here's what I would like to happen, and perhaps this is what happens (I just don't know, so I'm asking the question :-)

When the transaction B commits my-ref, I'd like transaction A to immediately stop (because the value of my-ref has changed) and restart with the new value. Is that what happens?

The reason I want this behavior is so that long-running-calculation doesn't waste all that CPU time on a calculation that is now obsolete.

I thought about using ensure, but I'm not sure how to use it in this context or if it is necessary.


Solution

  • It works as you fear.

    Stopping a thread in the JVM doing whatever it is doing requires a collaborative effort so there is no generic way for Clojure (or any other JVM language) to stop a running computation. The computation must periodically check a signal to see if it should stop itself. See How do you kill a thread in Java?.

    About how to implement it, I would say that is just too hard, so I would measure first if it is really really an issue. If it is, I would see if a traditional pessimistic lock is a better solution. If pessimistic locks is still not the solution, I would try to build something that runs the computation outside the transactions, use watchers on the refs and sets the refs conditionally after the computation if they have still the same value. Of course this runs outside the transactions boundaries and probably it is a lot more tricky that it sounds.

    About ensure, only refs that are being modified participate in the transaction, so you can suffer for write skew. See Clojure STM ambiguity factor for a longer explanation.