2011/9/5 Dominikus <[email protected]> > Right, it's "according to the spec", but it doesn't explain the > behavior. as I noticed this morning http://clojure.org/refs references > http://en.wikipedia.org/wiki/Multiversion_concurrency_control (the > technique used to implement STM) which provides an explanation for the > experienced behavior: > > <quote> > If a transaction (Ti) wants to write to an object, and if there is > another transaction (Tk), the timestamp of Ti must precede the > timestamp of Tk (i.e., TS(Ti) < TS(Tk)) for the object write operation > to succeed. Which is to say a write cannot complete if there are > outstanding transactions with an earlier timestamp. > </quote> > > That's why the second transaction, which comes later in time, is the > one waiting for the first transaction to complete. >
Thanks for the explanation. Though there's still something weird : it may be that there isn't enough context in the quote, because one could deduce from the quote that transactions (which write something, not just transactions which want to see consistent view of the refs world) are serialized ? Or should the sentence read as "and if there is another transaction (Tk) [wanting to write | having written] to the same object], " ? I really had the intution (which has now been proven false), that it would be the first transaction to complete which would win over the other transaction, had it started before or after it ... > > Dominikus > > On Aug 30, 9:53 am, Laurent PETIT <[email protected]> wrote: > > 2011/8/30 Kevin Downey <[email protected]> > > > > > the two threads race to acquire the write lock and the winner runs, > > > the loser retries. my guess is acquiring the write lock helps avoid > > > live locks between transactions. > > > > Yes, and after re-readinghttp://clojure.org/refs, the current behaviour > > does not seem to violate any of the 10 guaranteed listed points. > > > > So, while it seems unfortunate from client's code point of view, it is > > correct wrt to "the current specs". > > > > Now I more deeply understand why one should at all price avoid long > > computations from within a transaction ... > > > > Cheers, > > > > -- > > Laurent > > > > > > > > > > > > > > > > > > > > > On Mon, Aug 29, 2011 at 10:59 PM, Laurent PETIT < > [email protected]> > > > wrote: > > > > My tests were false. > > > > Since I sent the code "at once" to the REPL, there was a race > condition > > > > between the start of the agent and the dosync for setting the ref's > value > > > to > > > > 2000 ; this explains the weirdness of my test results (thank you, Mr > > > Murphy, > > > > for having made the tests look like beta2 behaved differently from > the > > > > others ...) > > > > So now I see consistent behaviours across 1.2.0, beta1 and beta2. > > > > But I am still perplex and confused by the behaviour ... > > > > > > 2011/8/30 Laurent PETIT <[email protected]> > > > > > >> ok so now i'm totally confused, since I cannot see why the behaviour > is > > > so > > > >> different between beta1 and beta 2 ... > > > > > >> 2011/8/30 Laurent PETIT <[email protected]> > > > > > >>> Congratulations, you've found a bug in clojure 1.3 beta2, see: > > > >>> look with my clojure 1.2.0 version : > > > >>> ;; Clojure 1.2.0 > > > >>> => (def thread (agent "Thread")) > > > >>> (def account (ref 1000)) > > > >>> (send thread > > > >>> (fn [agt aref] (dosync (alter aref + 100) (Thread/sleep > 8000) > > > >>> agt)) > > > >>> account) > > > >>> (time (dosync (ref-set account 2000))) > > > >>> #'user/thread > > > >>> #'user/account > > > >>> #<Agent@7e543cb1: "Thread"> > > > >>> "Elapsed time: 0.498106 msecs" > > > >>> 2000 > > > >>> => ;; 10 seconds later : > > > >>> => @account > > > >>> 2100 > > > > > >>> And now with clojure 1.3 beta1 : > > > >>> ;; Clojure 1.3.0-beta1 > > > >>> => (def thread (agent "Thread")) > > > >>> (def account (ref 1000)) > > > >>> (send thread > > > >>> (fn [agt aref] (dosync (alter aref + 100) (Thread/sleep > 8000) > > > >>> agt)) > > > >>> account) > > > >>> (time (dosync (ref-set account 2000))) > > > >>> ;; 10 seconds later : > > > >>> #'user/thread > > > >>> #'user/account > > > >>> #<Agent@6bf51e5c: "Thread"> > > > >>> "Elapsed time: 0.270225 msecs" > > > >>> 2000 > > > >>> => @account > > > >>> 2100 > > > >>> And now with Clojure 1.3-beta2 > > > >>> ;; Clojure 1.3.0-beta2 > > > >>> => (def thread (agent "Thread")) > > > >>> (def account (ref 1000)) > > > >>> (send thread > > > >>> (fn [agt aref] (dosync (alter aref + 100) (Thread/sleep > 8000) > > > >>> agt)) > > > >>> account) > > > >>> (time (dosync (ref-set account 2000))) > > > >>> #'user/thread > > > >>> #'user/account > > > >>> #<Agent@50fba502: "Thread"> > > > >>> "Elapsed time: 7957.328798 msecs" > > > >>> 2000 > > > >>> ;; 10 seconds later : > > > >>> => @account > > > >>> 2000 > > > >>> ;; 10 more seconds later : > > > >>> => @account > > > >>> 2000 > > > >>> 2011/8/30 Laurent PETIT <[email protected]> > > > > > >>>> 2011/8/29 Dominikus <[email protected]> > > > > > >>>>> Thanks a lot for this detailed analysis and the pointers to the > Java > > > >>>>> implementation, Stefan! That's excellent and very helpful. > > > > > >>>>> I still wonder, why the first transaction blocks write access. My > > > >>>>> overall understanding of the transaction system is that changes > to > > > >>>>> referenced values remain in-transaction before committing them, a > > > >>>>> write-lock shouldn't be needed. I'm surprised, that the second > > > >>>>> transaction is the one who has to retry 71 times even though the > > > first > > > >>>>> transaction hasn't committed anything yet. > > > > > >>>> I must confess this behaviour also challenges my assumptions. > > > >>>> Does this work the same whatever the clojure version used ? > > > > > >>>>> Cheers, > > > > > >>>>> Dominikus > > > > > >>>>> P.S.: Thanks for the idea to use 'future' to spawn a thread! > > > > > >>>>> On Aug 29, 5:42 pm, Stefan Kamphausen <[email protected]> > > > wrote: > > > >>>>> > The call to alter already wrote to the Ref, this requires a > > > >>>>> > write-lock on > > > >>>>> > the ref (see LockingTransaction.java/doSet). After that it > sleeps > > > a > > > >>>>> > while > > > >>>>> > and gets to its commit phase. The other transactions retries > in > > > the > > > >>>>> > meantime. Consider the following code, which introduces some > atoms > > > >>>>> > for > > > >>>>> > counting the retries > > > > > >>>>> > (defn tz [] > > > >>>>> > (let [rr (ref 10) > > > >>>>> > a1 (atom 0) > > > >>>>> > a2 (atom 0)] > > > >>>>> > (println "Starting future") > > > >>>>> > (future > > > >>>>> > (dosync > > > >>>>> > (swap! a1 inc) > > > >>>>> > (alter rr + 100) > > > >>>>> > (Thread/sleep 8000))) > > > >>>>> > (println "Sleeping a bit") > > > >>>>> > (Thread/sleep 1000) > > > >>>>> > (println "Another dosync") > > > >>>>> > (time > > > >>>>> > (dosync > > > >>>>> > (swap! a2 inc) > > > >>>>> > (ref-set rr 1))) > > > >>>>> > [@rr @a1 @a2 (.getHistoryCount rr)])) > > > > > >>>>> > user> (tz) > > > >>>>> > Starting future > > > >>>>> > Sleeping a bit > > > >>>>> > Another dosync > > > >>>>> > "Elapsed time: 7001.554 msecs" > > > >>>>> > [1 1 71 0] > > > > > >>>>> > Note, how the 71 retries nice fit the approximately 7 seconds > left > > > >>>>> > and the > > > >>>>> > 100 miliseconds time-out (LOCK_WAIT_MSECS) for the lock used > in > > > >>>>> > LockingTransaction.java/tryWriteLock. > > > > > >>>>> > Transactions with significantly different run-times should be > > > >>>>> > avoided, > > > >>>>> > although there is some point at which older transactions will > get > > > >>>>> > their > > > >>>>> > turn. This could be another explanation of the effect. Take a > > > look > > > >>>>> > at the > > > >>>>> > barge-function in LockingTransaction.java > > > > > >>>>> > Hope this helps, > > > >>>>> > Stefan > > > > > >>>>> -- > > > >>>>> You received this message because you are subscribed to the > Google > > > >>>>> Groups "Clojure" group. > > > >>>>> To post to this group, send email to [email protected] > > > >>>>> Note that posts from new members are moderated - please be > patient > > > with > > > >>>>> your first post. > > > >>>>> To unsubscribe from this group, send email to > > > >>>>> [email protected] > > > >>>>> For more options, visit this group at > > > >>>>>http://groups.google.com/group/clojure?hl=en > > > > > > -- > > > > You received this message because you are subscribed to the Google > > > > Groups "Clojure" group. > > > > To post to this group, send email to [email protected] > > > > Note that posts from new members are moderated - please be patient > with > > > your > > > > first post. > > > > To unsubscribe from this group, send email to > > > > [email protected] > > > > For more options, visit this group at > > > >http://groups.google.com/group/clojure?hl=en > > > > > -- > > > And what is good, Phaedrus, > > > And what is not good— > > > Need we ask anyone to tell us these things? > > > > > -- > > > You received this message because you are subscribed to the Google > > > Groups "Clojure" group. > > > To post to this group, send email to [email protected] > > > Note that posts from new members are moderated - please be patient with > > > your first post. > > > To unsubscribe from this group, send email to > > > [email protected] > > > For more options, visit this group at > > >http://groups.google.com/group/clojure?hl=en > > -- > You received this message because you are subscribed to the Google > Groups "Clojure" group. > To post to this group, send email to [email protected] > Note that posts from new members are moderated - please be patient with > your first post. > To unsubscribe from this group, send email to > [email protected] > For more options, visit this group at > http://groups.google.com/group/clojure?hl=en > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to [email protected] Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/clojure?hl=en
