Ensure

41 views
Skip to first unread message

Mark Engelberg

unread,
Mar 15, 2010, 1:47:32 AM3/15/10
to clojure
What's a minimal example that demonstrates when ensure is necessary?

Michał Marczyk

unread,
Mar 15, 2010, 2:28:40 AM3/15/10
to clo...@googlegroups.com
On 15 March 2010 06:47, Mark Engelberg <mark.en...@gmail.com> wrote:
> What's a minimal example that demonstrates when ensure is necessary?

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ł

Meikel Brandmeyer

unread,
Mar 15, 2010, 2:35:46 AM3/15/10
to Clojure
Hi,

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

Mark Engelberg

unread,
Mar 15, 2010, 3:02:26 AM3/15/10
to clojure
Michal, aren't those examples backwards?  I would expect the ensure version to be the one that needs to retry, and the deref version to be the one that doesn't.

2010/3/14 Michał Marczyk <michal....@gmail.com>

Mark Engelberg

unread,
Mar 15, 2010, 3:07:01 AM3/15/10
to clojure
2010/3/14 Meikel Brandmeyer <m...@kotka.de>
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.


OK, I think I've finally figured out why I've always found ensure to be a bit confusing.  I often see ensure described like you did above, that you use it if you "need the value to be constant through the transaction."  I was puzzled, because I thought you were already guaranteed that the value is constant by the semantics promised by the STM system, "All reads of Refs will see a consistent snapshot of the 'Ref world' as of the starting point of the transaction (its 'read point')."

So what I'm finally realizing is that although you are guaranteed a consistent read value throughout the transaction, that value might become out of touch with "reality" unless you use ensure.

So I think I get it now, although Michal's examples make no sense to me, so maybe I'm still missing something.

Michał Marczyk

unread,
Mar 15, 2010, 3:14:35 AM3/15/10
to clo...@googlegroups.com
On 15 March 2010 08:07, Mark Engelberg <mark.en...@gmail.com> wrote:
> So what I'm finally realizing is that although you are guaranteed a
> consistent read value throughout the transaction, that value might become
> out of touch with "reality" unless you use ensure.

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ł

Michał Marczyk

unread,
Mar 15, 2010, 3:19:40 AM3/15/10
to clo...@googlegroups.com
2010/3/15 Mark Engelberg <mark.en...@gmail.com>:

> Michal, aren't those examples backwards?  I would expect the ensure version
> to be the one that needs to retry, and the deref version to be the one that
> doesn't.

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ł

Mark Engelberg

unread,
Mar 15, 2010, 3:44:59 AM3/15/10
to clojure
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?...

(dosync
 (when (= @reactor-state :off)
   (alter reactor-door open)
   @reactor-state))

Christophe Grand

unread,
Mar 15, 2010, 4:13:46 AM3/15/10
to clo...@googlegroups.com
On Mon, Mar 15, 2010 at 8:44 AM, Mark Engelberg <mark.en...@gmail.com> wrote:
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.


It hasn't blocked in the sense of "wait and continue": it retries the transaction after growing the history of the ref (refs have an adaptive history):
(defn funk [r]
  (dosync
    (println "1st read" @r)
    (Thread/sleep 5000)
    (println "2nd read" @r)))
user=> (def r (ref 1))
#'user/r
user=> (future (funk r))
1st read 1
#<core$future_call$reify__3010@64cc4d: :pending>
user=> (dosync (ref-set r 5))
5
1st read 5  ;; ok it's retrying
2nd read 5
;; let's try again:
user=> (dosync (ref-set r 1))
1
user=> (future (funk r))
1st read 1
#<core$future_call$reify__3010@1b993d6: :pending>
user=> (dosync (ref-set r 5))
5
2nd read 1 ;; no retry this time!


you can create a ref with a given minimal history:
user=> (def r (ref 1 :min-history 1))
#'user/r
user=> (future (funk r))
#<core$future_call$reify__3010@502a39: :pending>
1st read 1
user=> (dosync (ref-set r 5))
5
2nd read 1 ;; see, no retry

From the end of (doc ref):
  Normally refs accumulate history dynamically as needed to deal with
  read demands. If you know in advance you will need history you can
  set :min-history to ensure it will be available when first needed (instead
  of after a read fault). History is limited, and the limit can be set
  with :max-history.

If derefs really can force a retry, can't Meikel's example be rewritten without ensure by just deref-ing again at the end?...

(dosync
 (when (= @reactor-state :off)
   (alter reactor-door open)
   @reactor-state))



No because once reactor-state has enough history it won't retry.
Even if you set :max-history to 0 it won't guarantee the retry because the value of reactor-state can change between the last read and the moment the commit is performed.

hth,

 Christophe

Mark Engelberg

unread,
Mar 15, 2010, 5:16:53 AM3/15/10
to clojure
Wow, the STM works far, far differently than I imagined, but I think I get it now.  The adaptive history clarification helped tremendously.  Thanks,

Mark

Michał Marczyk

unread,
Mar 15, 2010, 4:52:38 PM3/15/10
to clo...@googlegroups.com
On 15 March 2010 09:13, Christophe Grand <chris...@cgrand.net> wrote:
> It hasn't blocked in the sense of "wait and continue": it retries the
> transaction after growing the history of the ref (refs have an adaptive
> history):

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ł

Michał Marczyk

unread,
Mar 15, 2010, 4:53:40 PM3/15/10
to clo...@googlegroups.com
On 15 March 2010 08:44, Mark Engelberg <mark.en...@gmail.com> wrote:
> Thanks Michal, those annotations helped quite a bit.

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ł

Raoul Duke

unread,
Mar 15, 2010, 6:31:46 PM3/15/10
to clo...@googlegroups.com
hi Mark,

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.

Raoul Duke

unread,
Mar 15, 2010, 6:34:04 PM3/15/10
to clo...@googlegroups.com
On Mon, Mar 15, 2010 at 1:13 AM, Christophe Grand <chris...@cgrand.net> wrote:
> It hasn't blocked in the sense of "wait and continue": it retries the
> transaction after growing the history of the ref (refs have an adaptive
> history):

i'm confused about being able to set :max-history, does that not run
the risk of breaking the correctness of things?

thanks.

Jim Blomo

unread,
Mar 19, 2010, 11:43:02 AM3/19/10
to clo...@googlegroups.com

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

Reply all
Reply to author
Forward
0 new messages