以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).
这就是整个代码执行过程,有点复杂,而且我也还没理解清楚,先记录以后慢慢再整理
浙公网安备 33010602011771号