以mnesia:write为例子初步阅读mnesia源码
本来想全面解读一下mnesia源码,初步读了一遍,发现功底不够,先做个记录
先以网上的一张图看看
1、 我们先看看mnesia的启动
直接看mnesia_sup模块
%%%%下面摘自mnesia_sup.erl
.......
init([[]]) -> init(); init(BadArg) -> {error, {badarg, BadArg}}. init() -> Flags = {one_for_all, 0, 3600}, % Should be rest_for_one policy Event = event_procs(), Ext = ext_procs(), Kernel = kernel_procs(), {ok, {Flags, Event ++ Ext ++ Kernel}}. event_procs() -> KillAfter = timer:seconds(30), KA = mnesia_kernel_sup:supervisor_timeout(KillAfter), E = mnesia_event, [{E, {?MODULE, start_event, []}, permanent, KA, worker, [E, gen_event]}]. kernel_procs() -> K = mnesia_kernel_sup, KA = infinity, [{K, {K, start, []}, permanent, KA, supervisor, [K, supervisor]}]. ext_procs() -> K = mnesia_ext_sup, KA = infinity, [{K, {K, start, []}, permanent, KA, supervisor, [K, supervisor]}].
...........
2、 根据名称就可以看到mnesia进程主要分了3块 event_procs、kernel_procs、ext_procs,分别为事件模块,核心模块和其他模块
事件模块就是注册了一个gen_event,并把mnesia_event注册上,具体代码如下
%%%%下面摘自mnesia_sup.erl
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% event handler start_event() -> case gen_event:start_link({local, mnesia_event}) of {ok, Pid} -> case add_event_handler() of ok -> {ok, Pid}; Error -> Error end; Error -> Error end. add_event_handler() -> Handler = mnesia_monitor:get_env(event_module), gen_event:add_handler(mnesia_event, Handler, []).
3、 ext_procs默认是空,所以下面重点讲解核心模块(叫核心当然是重点😄)
%%%%下面摘自mnesia_kernel_sup.erl
% sub supervisor callback functions init([]) -> ProcLib = [mnesia_monitor, proc_lib], Flags = {one_for_all, 0, timer:hours(24)}, % Trust the top supervisor Workers = [worker_spec(mnesia_monitor, timer:seconds(3), [gen_server]), worker_spec(mnesia_subscr, timer:seconds(3), [gen_server]), worker_spec(mnesia_locker, timer:seconds(3), ProcLib), worker_spec(mnesia_recover, timer:minutes(3), [gen_server]), worker_spec(mnesia_tm, timer:seconds(30), ProcLib), worker_spec(mnesia_rpc, timer:seconds(3), [gen_server]), supervisor_spec(mnesia_checkpoint_sup), worker_spec(mnesia_controller, timer:seconds(3), [gen_server]), worker_spec(mnesia_late_loader, timer:seconds(3), ProcLib) ], {ok, {Flags, Workers}}. worker_spec(Name, KillAfter, Modules) -> KA = supervisor_timeout(KillAfter), {Name, {Name, start, []}, permanent, KA, worker, [Name] ++ Modules}. supervisor_spec(Name) -> {Name, {Name, start, []}, permanent, infinity, supervisor, [Name, supervisor]}.
4、 mnesia_kernel_sup启动了一系列的进程,具体这些进程怎么协作的就要进一步阅读了(目前我还是懵的)
看了半天发现还是不能清晰的理解,就新建了一个表,然后看看mnesia:write过程代码执行过程进行记录
执行的代码如下
-record(person, {idno, name, age, occupation}). mnesia:create_table(person,[{disc_copies, [node()]},{attributes,record_info(fields,person)}]) F = fun() -> Acc = #person{idno = 16, name="jlk,j", age=13, occupation="fr"}, mnesia:write(Acc) end. mnesia:transaction(F).
5、这次测试的是表的类型是 disc_copies,我们只分 mnesia:write(Acc)和mnesia:transaction(F) 这2步
disc_copies表 副本上的写操作分两步执行。首先将写入操作附加到日志文件,然后在RAM中执行实际操作。
%%%%下面摘自mnesia.erl
write(Val) when is_tuple(Val), tuple_size(Val) > 2 ->
Tab = element(1, Val),
write(Tab, Val, write);
write(Tab, Val, LockKind) ->
case get(mnesia_activity_state) of
{?DEFAULT_ACCESS, Tid, Ts} ->
write(Tid, Ts, Tab, Val, LockKind);
{Mod, Tid, Ts} ->
Mod:write(Tid, Ts, Tab, Val, LockKind);
_ ->
abort(no_transaction)
end.
write(Tid, Ts, Tab, Val, LockKind)
when is_atom(Tab), Tab /= schema, is_tuple(Val), tuple_size(Val) > 2 ->
case element(1, Tid) of
ets ->
?ets_insert(Tab, Val),
ok;
tid ->
Store = Ts#tidstore.store,
Oid = {Tab, element(2, Val)},
case LockKind of
write ->
mnesia_locker:wlock(Tid, Store, Oid);
sticky_write ->
mnesia_locker:sticky_wlock(Tid, Store, Oid);
_ ->
abort({bad_type, Tab, LockKind})
end,
write_to_store(Tab, Store, Oid, Val);
Protocol ->
do_dirty_write(Protocol, Tab, Val)
end;
write_to_store(Tab, Store, Oid, Val) ->
{_, _, Type} = mnesia_lib:validate_record(Tab, Val),
Oid = {Tab, element(2, Val)},
case Type of
bag ->
?ets_insert(Store, {Oid, Val, write});
_ ->
?ets_delete(Store, Oid),
?ets_insert(Store, {Oid, Val, write})
end,
ok.
6、我们可以看到mnesia:write(Acc)执行过程非常清晰(注意上面mnesia:write(Acc)语句定义Fun的时候其实还没有执行这个代码,只是定义一个这样的匿名函数)
mnesia:write(Acc)就是申请了写锁,然后把数据写入了store表,具体函数跳转大概如下
mnesia:write(Acc) -> mnesia:write(Tab, Val, LockKind) (person, Acc, write) -> mnesia:write(Tid, Ts, Tab, Val, LockKind) (同上) ->
mnesia:write_to_store(Tab, Store, Oid, Val) (person, Ts#tidstore.store, {person,15}, Acc) ->
write_to_store(Tab, Store, Oid, Val)
开始我还以为这个Store就是person表,后面发现根本不是,至于是什么这里可以看出是
{Mod, Tid, Ts} = erlang:get(mnesia_activity_state)
这样取到的,具体的就要分析mnesia:transaction(F)了
%%%%下面摘自mnesia.erl
transaction(Fun) -> transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, async). %%这里的还不存在,get取得的是undefined transaction(State, Fun, Args, Retries, Mod, Kind) when is_function(Fun), is_list(Args), Retries == infinity, is_atom(Mod) -> mnesia_tm:transaction(State, Fun, Args, Retries, Mod, Kind);
%%%%下面摘自mnesia_tm.erl
transaction(OldTidTs, Fun, Args, Retries, Mod, Type) -> Factor = 1, case OldTidTs of undefined -> % Outer %%这里的mnesia_activity_state还是undefined execute_outer(Mod, Fun, Args, Factor, Retries, Type); {_, _, non_transaction} -> % Transaction inside ?sync_dirty Res = execute_outer(Mod, Fun, Args, Factor, Retries, Type), put(mnesia_activity_state, OldTidTs), Res; {OldMod, Tid, Ts} -> % Nested execute_inner(Mod, Tid, OldMod, Ts, Fun, Args, Factor, Retries, Type); _ -> % Bad nesting {aborted, nested_transaction} end. execute_outer(Mod, Fun, Args, Factor, Retries, Type) -> case req(start_outer) of {error, Reason} -> {aborted, Reason}; {new_tid, Tid, Store} -> Ts = #tidstore{store = Store}, NewTidTs = {Mod, Tid, Ts}, put(mnesia_activity_state, NewTidTs), execute_transaction(Fun, Args, Factor, Retries, Type) end. %%req/1和rec/1就是向mnesia_tm的server进程请求并取得回应 req(R) -> case whereis(?MODULE) of undefined -> {error, {node_not_running, node()}}; Pid -> Ref = make_ref(), Pid ! {{self(), Ref}, R}, rec(Pid, Ref) end. rec(Pid, Ref) -> receive {?MODULE, Ref, Reply} -> Reply; {'EXIT', Pid, _} -> {error, {node_not_running, node()}} end. %%这是mnesia_tm模块的server主进程,对start_outer的回应 doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor=Sup}=State) -> ....... {From, start_outer} -> %% Create and associate ets_tab with Tid try ?ets_new_table(mnesia_trans_store, [bag, public]) of Etab -> %%这就是后续的{Mod, Tid, Ts}中的Ts的store tmlink(From), C = mnesia_recover:incr_trans_tid_serial(), %%更新mnesia_decision表的数量,暂时不知道干嘛的 ?ets_insert(Etab, {nodes, node()}), Tid = #tid{pid = tmpid(From), counter = C}, %%这就是后续的{Mod, Tid, Ts}中的Tid A2 = gb_trees:insert(Tid,[Etab],Coordinators), S2 = State#state{coordinators = A2}, reply(From, {new_tid, Tid, Etab}, S2) catch error:Reason -> %% system limit Msg = "Cannot create an ets table for the " "local transaction store", reply(From, {error, {system_limit, Msg, Reason}}, State) end; ....... %%这里的Fun是mnesia:write, 其中Fun:#Fun<erl_eval.45.97283095>, Args:[], Type:async execute_transaction(Fun, Args, Factor, Retries, Type) -> try apply_fun(Fun, Args, Type) of {atomic, Value} -> mnesia_lib:incr_counter(trans_commits), erase(mnesia_activity_state), %% no need to clear locks, already done by commit ... %% Flush any un processed mnesia_down messages we might have flush_downs(), ?SAFE(unlink(whereis(?MODULE))), {atomic, Value}; {do_abort, Reason} -> check_exit(Fun, Args, Factor, Retries, {aborted, Reason}, Type); {nested_atomic, Value} -> mnesia_lib:incr_counter(trans_commits), {atomic, Value} catch throw:Value -> %% User called throw Reason = {aborted, {throw, Value}}, return_abort(Fun, Args, Reason); error:Reason:ST -> check_exit(Fun, Args, Factor, Retries, {Reason,ST}, Type); _:Reason -> check_exit(Fun, Args, Factor, Retries, Reason, Type) end. apply_fun(Fun, Args, Type) -> Result = apply(Fun, Args), %%mnesia:write执行 case t_commit(Type) of do_commit -> {atomic, Result}; do_commit_nested -> {nested_atomic, Result}; {do_abort, {aborted, Reason}} -> {do_abort, Reason}; {do_abort, _} = Abort -> Abort end. %%N:1, Prep:{prep,sym_trans,[{commit,nonode@nohost,presume_commit,[],[{{person,16},{person,16,"jlk,j",13,"fr"},write}],[],[],[]}],person,[{nonode@nohost,disc_copies}],[],[{nonode@nohost,disc_copies}],[],false} t_commit(Type) -> {_Mod, Tid, Ts} = get(mnesia_activity_state), Store = Ts#tidstore.store, if Ts#tidstore.level == 1 -> intercept_friends(Tid, Ts), %% N is number of updates case arrange(Tid, Store, Type) of {N, Prep} when N > 0 -> multi_commit(Prep#prep.protocol, majority_attr(Prep), Tid, Prep#prep.records, Store); {0, Prep} -> multi_commit(read_only, majority_attr(Prep), Tid, Prep#prep.records, Store) end; true -> %% nested commit Level = Ts#tidstore.level, [{OldMod,Obsolete} | Tail] = Ts#tidstore.up_stores, req({del_store, Tid, Store, Obsolete, false}), NewTs = Ts#tidstore{store = Store, up_stores = Tail, level = Level - 1}, NewTidTs = {OldMod, Tid, NewTs}, put(mnesia_activity_state, NewTidTs), do_commit_nested end. %%CR:[{commit,nonode@nohost,presume_commit,[],[{{person,16},{person,16,"jlk,j",13,"fr"},write}],[],[],[]}] %%这个函数操作比较多,主要是提交事物,并同步到集群,具体原理如何后面再详细解读 %%目前没有太看懂这个 multi_commit(sym_trans, _Maj = [], Tid, CR, Store) -> {DiscNs, RamNs} = commit_nodes(CR, [], []), Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), ?ets_insert(Store, Pending), {WaitFor, Local} = ask_commit(sym_trans, Tid, CR, DiscNs, RamNs), {Outcome, []} = rec_all(WaitFor, Tid, do_commit, []), ?eval_debug_fun({?MODULE, multi_commit_sym}, [{tid, Tid}, {outcome, Outcome}]), rpc:abcast(DiscNs -- [node()], ?MODULE, {Tid, Outcome}), rpc:abcast(RamNs -- [node()], ?MODULE, {Tid, Outcome}), case Outcome of do_commit -> mnesia_recover:note_decision(Tid, committed), do_dirty(Tid, Local), %%这个是提交数据到本地磁盘(dets)或者内存(ets) mnesia_locker:release_tid(Tid), ?MODULE ! {delete_transaction, Tid}; {do_abort, _Reason} -> mnesia_recover:note_decision(Tid, aborted) end, ?eval_debug_fun({?MODULE, multi_commit_sym, post}, [{tid, Tid}, {outcome, Outcome}]), Outcome; %%Tid:{tid,82,<0.80.0>}, Commit:{commit,nonode@nohost,presume_commit,[],[{{person, 16},{person,16,"jlk,j",13,"fr"},write}],[],[],[]} do_dirty(Tid, Commit) when Commit#commit.schema_ops == [] -> mnesia_log:log(Commit), do_commit(Tid, Commit). %% mnesia_log.erl %% Write commit records to the latest_log log(C) -> case need_log(C) andalso mnesia_monitor:use_dir() of true -> if is_record(C, commit) -> append(latest_log, strip_snmp(C)); true -> %% Either a commit record as binary %% or some decision related info append(latest_log, C) end, mnesia_dumper:incr_log_writes(); false -> ignore end. %% do_commit(Tid, CommitRecord) do_commit(Tid, Bin) when is_binary(Bin) -> do_commit(Tid, binary_to_term(Bin)); do_commit(Tid, C) -> do_commit(Tid, C, optional). do_commit(Tid, Bin, DumperMode) when is_binary(Bin) -> do_commit(Tid, binary_to_term(Bin), DumperMode); do_commit(Tid, C, DumperMode) -> mnesia_dumper:update(Tid, C#commit.schema_ops, DumperMode), R = do_snmp(Tid, proplists:get_value(snmp, C#commit.ext, [])), R2 = do_update(Tid, ram_copies, C#commit.ram_copies, R), R3 = do_update(Tid, disc_copies, C#commit.disc_copies, R2), R4 = do_update(Tid, disc_only_copies, C#commit.disc_only_copies, R3), R5 = do_update_ext(Tid, C#commit.ext, R4), mnesia_subscr:report_activity(Tid), R5. %% Update the items do_update(Tid, Storage, [Op | Ops], OldRes) -> try do_update_op(Tid, Storage, Op) of ok -> do_update(Tid, Storage, Ops, OldRes); NewRes -> do_update(Tid, Storage, Ops, NewRes) catch _:Reason:ST -> %% This may only happen when we recently have %% deleted our local replica, changed storage_type %% or transformed table %% BUGBUG: Updates may be lost if storage_type is changed. %% Determine actual storage type and try again. %% BUGBUG: Updates may be lost if table is transformed. verbose("do_update in ~w failed: ~tp -> {'EXIT', ~tp}~n", [Tid, Op, {Reason, ST}]), do_update(Tid, Storage, Ops, OldRes) end; do_update(_Tid, _Storage, [], Res) -> Res. do_update_op(Tid, Storage, {{Tab, K}, Obj, write}) -> commit_write(?catch_val({Tab, commit_work}), Tid, Storage, Tab, K, Obj, undefined), mnesia_lib:db_put(Storage, Tab, Obj); %%mnesia_lib.erl db_put(ram_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; db_put(disc_copies, Tab, Val) -> ?ets_insert(Tab, Val), ok; db_put(disc_only_copies, Tab, Val) -> dets:insert(Tab, Val); db_put({ext, Alias, Mod}, Tab, Val) -> Mod:insert(Alias, Tab, Val).
这就是整个代码执行过程,有点复杂,而且我也还没理解清楚,先记录以后慢慢再整理