mnesia使用"wait-die"机制预防死锁,"wait-die"是基于时间戳的,mnesia采用了lamport clock算法作为"wait-die"机制的时间戳。
Lamport clock是解决分布式系统中事件发生时序的一种方式。 Lamport定义了一个关系叫做happens-before,记为-> 。a->b意味着所有的进程都同意事件a发生在事件b之前。在以下两种情况下,可以很容易的得到这个关系:
1)如果事件a和事件b是同一个进程中的并且事件a发生在事件b前面,那么a->b
2)如果进程A发送一条消息m给进程B,a代表进程A发送消息m的事件,b代表进程B接收消息m的事件,那么a->b
Lamport logical clock算法大致实现为:
每个进程Pi维护一个本地计数器Ci,相当于logical clock,按照以下规则更新Ci:
1)每次执行一个事件(例如通过网络发送消息,或者将消息交换给应用层,或者其他的一些内部事件 )之前,将Ci加1
2)当进程Pi发送消息m给Pj的时候,在消息m上附上Ci
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时、事务过程中出现异常,向其他节点询问事务结果时也都会进行事务计数器的更新。