Here's a link to my posting to an earlier thread with a minimal
example of how ensure is different from deref:
http://groups.google.com/group/clojure/msg/8e477df4b5cf418e
Is this what you're looking for?
Sincerely,
Michał
On Mar 15, 6:47 am, Mark Engelberg <mark.engelb...@gmail.com> wrote:
> What's a minimal example that demonstrates when ensure is necessary?
Ok. Maybe a little grotesque and not 100% valid, but it should get
across the idea.
(dosync
(when (= @reactor-state :off)
(alter reactor-door open)))
Once every 100 years we will open the reactor door while the reactor
is turned on, because between the deref of the reactor state and the
open command for the door some other transaction might turn on the
reactor. (That is: the transaction will not retry if the alter
succeeded.)
(dosync
(when (= (ensure reactor-state) :off)
(alter reactor-door open)))
This is safe, because the transaction retries even if the alter
succeeded. Namely in the case that the value of reactor state changed.
In short: ensure is used when you require only read access to a Ref,
but need the value to be constant through the transaction. If you
modify the a Ref via alter or ref-set, the ensure is not necessary.
Sincerely
Meikel
In short: ensure is used when you require only read access to a Ref,
but need the value to be constant through the transaction. If you
modify the a Ref via alter or ref-set, the ensure is not necessary.
That's not true actually. For example:
user> (def r (ref 0))
#'user/r
user> (future (println @r) (Thread/sleep 5000) (println @r))
0
#<core$future_call$reify__6095@1aa5f9b: :pending>
user> (dosync (ref-set r 5))
5
5
Note that you have to type in the dosync before the future thread wakes up.
> So I think I get it now, although Michal's examples make no sense to me, so
> maybe I'm still missing something.
One thing which may not be clear at a glance is that things are
happening on multiple threads and in the ensure version, the final
dosync blocks. Try it out at the REPL to see what I mean.
Sincerely,
Michał
Oh, I failed to notice this message, sorry... I've answered your reply
to Meikel first. Anyway, here's an annotated version of the examples:
1. ensure version:
user> (defn funk [r]
(dosync
(println (ensure r))
(Thread/sleep 5000)
(println (ensure r))))
#'user/funk
user> (def r (ref 1))
#'user/r
user> (.start (Thread. #(funk r)))
1
nil
user> (dosync (ref-set r 5))
;;; this dosync blocks
1
;;; the background transaction is done, the dosync with the ref-set continues
5 ; the return value from ref-set
2. deref version:
user> (defn funk [r]
(dosync
(println @r)
(Thread/sleep 5000)
(println @r)))
#'user/funk
user> (def r (ref 1))
#'user/r
user> (.start (Thread. #(funk r)))
1
nil
user> (dosync (ref-set r 5))
;;; the dosync doesn't block -- it changes r to point to 5 immediately
5 ; this is the return value from the ref-set
5 ; the background transaction now tries to deref r again,
; only to notice that it's been changed behind it back;
; thus it needs to retry; this 5 is printed in the "second
; attempt" / retried transaction
5 ; this is the second 5 coming out from the retried transaction
HTH.
Sincerely,
Michał
Thanks Michal, those annotations helped quite a bit. It also demonstrates to me that my mental model of how the STM works is quite out of touch with how it actually does.
I'm especially surprised that derefs work the way your example illustrates. From the STM docs, it really sounded like derefs always give you the value as of the snapshot from the start of the transaction. Your example also seems to violate this principle from the STM doc:
"Writers will never block commuters, or readers."
Your example shows that a write to a ref has effectively blocked (by forcing a retry) of a transaction which only reads the ref.
If derefs really can force a retry, can't Meikel's example be rewritten without ensure by just deref-ing again at the end?...@reactor-state))
(dosync
(when (= @reactor-state :off)
(alter reactor-door open)
Thanks a bunch for pointing this one out! I sort of recall reading
ref's docs and not really getting this bit some time ago... Hopefully
now I can integrate this new piece into the STM jigsaw puzzle in my
head.
Sincerely,
Michał
You're welcome!
> It also demonstrates
> to me that my mental model of how the STM works is quite out of touch with
> how it actually does.
I rather think I'm in that situation myself. Take the "Long running
STM updates" thread, for example; that's a new one for me.
On 15 March 2010 10:16, Mark Engelberg <mark.en...@gmail.com> wrote:
> Wow, the STM works far, far differently than I imagined, but I think I get
> it now. The adaptive history clarification helped tremendously. Thanks,
I second that. :-)
All the best,
Michał
per chance, might you be willing/able to summarize the latest mental
model you have, for those of us still not entirely enlightened, but
trying to learn? i'm re-reading these messages in the mean time... :-)
sincerely.
i'm confused about being able to set :max-history, does that not run
the risk of breaking the correctness of things?
thanks.
No, my understanding is that correctness is guaranteed by the retry.
The adaptive history is an optimization so that the retry doesn't need
to happen. As Christophe demonstrated, the first transaction
essentially ran with a :max-history of 0 and the transaction had to
retry. The next time around, the ref had adequate history to store
its previous state so that a retry was not needed.
Jim