Are refs really consistent within a STM transaction?

二次信任 提交于 2019-12-10 16:20:37

问题


I read at clojure.org/refs that

All reads of Refs will see a consistent snapshot of the 'Ref world' as of the starting point of the transaction (its 'read point'). The transaction will see any changes it has made. This is called the in-transaction-value.

There's also a link to Snapshot Isolation on wikipedia that implies that reads of any number of refs will be consistent with each other once a transaction has started.

I made a test case...

(def r1 (ref 0))
(def r2 (ref 0))

(defn delay-then-inc-ref [id ref delay]
  (.start 
    (Thread. 
        #((println id " start")
          (Thread/sleep delay)
          (dosync
             (alter ref inc))
             (println id " end")))))

(defn deref-delay-deref [ref1 ref2 delay]
    (.start 
       (Thread. 
          #((println "S start")
            (dosync 
              (let [a @ref2]
                 (Thread/sleep delay)
                 (println "S r1=" @ref1))) ; @ref1 consistent with @ref2 ?
                 (println "S end")))))

*clojure-version*
;=> {:major 1, :minor 3, :incremental 0, :qualifier nil}
(deref-delay-deref r1 r2 2000) 
(delay-then-inc-ref "1" r1 500)
(delay-then-inc-ref "2" r1 1000)
(delay-then-inc-ref "3" r1 1500)

The output is:

S start
1 start
2 start
3 start
1 end
2 end
3 end
r1 = 3
S end
nil

The value of r1 = 3 rather than r1 = 0 suggests that in deref-delay-deref the deref of ref1 after the sleep is picking the value of r1 after the three delay-then-inc-ref transactions have occurred.

Note that I know about ensure to prevent updates to refs by other transactions during a particular transaction, but I don't believe that applies here. I don't care if ref1 changes as long as I see a value consistent with the start of my transaction.

How does this behaviour fit with the above referenced documentation?


回答1:


It turns out that if the ref has some history it behaves as I expect, so changing the ref declaration to add a :min-history and then setting both refs as shown, seems to make it work....

(def r1 (ref 0 :min-history 5))
(def r2 (ref 0 :min-history 5))

(dosync
 (ref-set r1 0)
 (ref-set r2 0))

Then the output is:

S start
1 start
1 end
2 start
2 end
3 start
3 end
S r1= 0
S end
nil

Reading here, it's clear what's going on. The read transaction is restarting because there is no entry in the ref history from before the transaction started. To confim I added some more logging:

(defn deref-delay-deref [ref1 ref2 delay]
    (.start 
       (Thread. 
          #((println "S start")
            (dosync
              (println "transaction starting")
              (let [a @ref2]
                 (Thread/sleep delay)
                 (println "S r1=" @ref1))) ; should be consistent with @ref2
            (println "S end")))))

Output without history mods:

S start
transaction starting
1 start
2 start
3 start
1 end
2 end
3 end
transaction starting
S r1= 3
S end

and with history mods:

S start
transaction starting
1 start
2 start
3 start
1 end
2 end
3 end
S r1= 0
S end
nil

UPDATE: It turns out my answer above is something of a distraction because of the artificial nature of the test case. In real world usage it doesn't matter whether the transaction re-starts or not, since the transactions MUST be written such that they are re-startable. The runtime provides no guarantees about whether read only transactions will complete in the presence/absence of history. Rather it can do whatever's necessary to get the world of transactions to complete and the transaction code MUST be written with this in mind. More detailed discussion here

I'm leaving the above for reference.



来源:https://stackoverflow.com/questions/10178639/are-refs-really-consistent-within-a-stm-transaction

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!