以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).

 这就是整个代码执行过程,有点复杂,而且我也还没理解清楚,先记录以后慢慢再整理

 

posted @ 2021-05-18 14:26  土豆008  阅读(156)  评论(0编辑  收藏  举报