Jason
Jason

Reputation: 12333

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.

Upvotes: 0

Views: 218

Answers (2)

amalloy
amalloy

Reputation: 92117

This doesn't happen, because...well, how could it? Your function long-running-calculation doesn't have any code in it to handle stopping prematurely, and that's the code that's being run at the time you want to cancel the transaction. So the only way to stop it would be to preemptively stop the thread from executing and forcibly restart it at some other location. This is terribly dangerous, as java.lang.Tread/stop discovered back in Java 1.1; the side effects could be a lot worse than some wasted CPU cycles.

refs do attempt to solve this problem, sorta: if there's one long-running transaction that has to restart itself many times because shorter transactions keep sneaking in, it will take a stronger lock and run to completion. But this is a pretty rare occurrence (heck, even needing to use refs is rare, and this is a rare way for refs to behave).

Upvotes: 2

DanLebrero
DanLebrero

Reputation: 8591

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.

Upvotes: 3

Related Questions