mnesia使用"wait-die"機制預防死鎖,"wait-die"是基於時間戳的,mnesia採用了lamport clock算法做爲"wait-die"機制的時間戳。 java
Lamport clock是解決分佈式系統中事件發生時序的一種方式。 Lamport定義了一個關係叫作happens-before,記爲-> 。a->b意味着全部的進程都贊成事件a發生在事件b以前。在如下兩種狀況下,能夠很容易的獲得這個關係: node
1)若是事件a和事件b是同一個進程中的而且事件a發生在事件b前面,那麼a->b 算法
2)若是進程A發送一條消息m給進程B,a表明進程A發送消息m的事件,b表明進程B接收消息m的事件,那麼a->b 網絡
Lamport logical clock算法大體實現爲: app
每一個進程Pi維護一個本地計數器Ci,至關於logical clock,按照如下規則更新Ci: 分佈式
1)每次執行一個事件(例如經過網絡發送消息,或者將消息交換給應用層,或者其餘的一些內部事件 )以前,將Ci加1 oop
2)當進程Pi發送消息m給Pj的時候,在消息m上附上Ci code
3)當接收進程Pj接收到進程Pi發送的消息時,更新本身的Cj = max{Cj,Ci} 進程
Lamport論文連接:http://www.stanford.edu/class/cs240/readings/lamport.pdf 事件
===========================================================
在mnesia中充當這個計數器的就是事務ID,事務ID統一由mnesia_tm進程負責初始化和更新。mnesia_locker進程就是根據比較事務ID的大小來執行"wait-die"機制和防止餓死現象的。
allowed_to_be_queued(WaitForTid, Tid) -> case get(pid_sort_order) of undefined -> WaitForTid > Tid; r9b_plain -> cmp_tid(true, WaitForTid, Tid) =:= 1; standard -> cmp_tid(false, WaitForTid, Tid) =:= 1 end.mnesia主要在如下幾種狀況下更新事務ID
1)本節點請求開始新事務時
在執行一個新事務前,會向mnesia_tm進程獲取一個事務ID和臨時存儲空間,mnesia_tm收到請求後會將事務ID計數器加1並賦給該事務。
mnesia_tm.erl: doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor=Sup}=State) -> receive ... {From, start_outer} -> case catch ?ets_new_table(mnesia_trans_store, [bag, public]) of Etab -> tmlink(From), C = mnesia_recover:incr_trans_tid_serial(), ?ets_insert(Etab, {nodes, node()}), Tid = #tid{pid = tmpid(From), counter = C}, ... mnesia_recover.erl: incr_trans_tid_serial() -> ?ets_update_counter(mnesia_decision, serial, 1).2)參與其餘節點發起的事務結束時
事務涉及多個節點時,事務發起者會將帶有本節點事務ID的信息告訴全部參與的節點。當參與節點處理事務結束時,無論是正常結束仍是異常結束,都會更新本節點的事務計數器。
mnesia_tm.erl: transaction_terminated(Tid) -> mnesia_checkpoint:tm_exit_pending(Tid), Pid = Tid#tid.pid, if node(Pid) == node() -> unlink(Pid); true -> %% Do the Lamport thing here mnesia_recover:sync_trans_tid_serial(Tid) end. mnesia_recover.erl: sync_trans_tid_serial(ThatCounter) when is_integer(ThatCounter) -> ThisCounter = trans_tid_serial(), if ThatCounter > ThisCounter -> set_trans_tid_serial(ThatCounter + 1); true -> ignore end; sync_trans_tid_serial(Tid) -> sync_trans_tid_serial(Tid#tid.counter). set_trans_tid_serial(Val) -> ?ets_insert(mnesia_decision, {trans_tid, serial, Val}).其餘狀況:例如,mnesia啓動進行dump時、事務過程當中出現異常,向其餘節點詢問事務結果時也都會進行事務計數器的更新。