0

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.

glts
  • 21,808
  • 12
  • 73
  • 94
Jason
  • 11,709
  • 9
  • 66
  • 82
  • Is this asking *When a transaction commits, are other pending transactions involving any ref the transaction changes canceled at once?*. – Thumbnail Aug 09 '14 at 09:30

2 Answers2

3

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.

Community
  • 1
  • 1
DanLebrero
  • 8,545
  • 1
  • 29
  • 30
  • I found out that calling `ensure` at the beginning of the transaction would lock `my-ref` so that those other short running transactions would fail to write until trans A finished. I don't want to use `ensure` like this, it doesn't solve my problem, but it is a useful bit of information on this topic. – Jason Aug 09 '14 at 15:24
  • So indeed, I decided that letting the long-running-calculation run to completion was a trade-off worth making in exchange for simpler code (e.g., not adding checks or creating some way to preemptively stop the thread). – Jason Aug 09 '14 at 15:26
2

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).

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • I found a couple statements in your answer revealing and useful (for instance, the bit about "if there's one long-running"). However, I found the first statement confusing: "This doesn't happen, because.. well, how could it?". What doesn't happen? I would up-vote this if that statement is removed or replaced. – Jason Aug 09 '14 at 15:19