Mnesia, more questions

Ulf Wiger ulf@REDACTED
Tue Aug 30 19:29:36 CEST 2005


This code from rdbms in jungerl is, I believe,
a generic, and safe way to register commit and
abort triggers. It'll work for nested transactions,
transaction restarts and caught inner transaction
aborts.

/Uffe


activity(Fun) ->
     %% We maintain our own transaction Id (for the "additional actions")
     Id = erlang:now(),
     F = fun() ->
setup_transaction(Id),
Fun()
end,
     %% We perform a catch on a wrapped call to activity/1. This may look
     %% funny, but we want to allow {'EXIT', Reason} as a return value from
     %% activity/1 (which could happen if the last expression of the fun
     %% is a catch expression. This doesn't mean that the transaction was
     %% aborted in mnesia's eyes.
     Result = (catch {ok, mnesia:activity(transaction, F, [], ?MODULE)}),
     handle_result(Result, get_transaction_levels()).


%% if functions are called during a transaction which have side-effects,
%% these functions may be used to "commit" or "undo" the effect.
%% multiple calls can be made during one transaction; the functions will
%% be called LIFO *after* the transaction is aborted or commited.
%%
register_rollback_action(Fun) ->
     register_action(rollback, Fun).

register_commit_action(Fun) ->
     register_action(commit, Fun).



setup_transaction(Id) ->
     Key = trans_level_key(),
     case get(Key) of
undefined ->
     put(Key, [Id]);
L = [Id|_] ->
     L;% this happens at transaction restart
L ->
     %% we're setting up an inner transaction -- push Id onto stack
     put(Key, [Id|L])
     end,
     CommitKey = additional_action_key(commit, Id),
     RollbackKey = additional_action_key(rollback, Id),
     put(CommitKey, []),
     put(RollbackKey, []),
     ok.


register_action(Type, Fun) ->
     Key = additional_action_key(Type),
     case get(Key) of
undefined ->
     exit(no_transaction);
[] ->
     put(Key, [Fun]);
Funs when list(Funs) ->
     put(Key, [Fun|Funs])
     end.

additional_action_key(Type) ->
     additional_action_key(Type, transaction_level()).

additional_action_key(Type, Level) ->
     {?MODULE, additional, Type, Level}.


transaction_level() ->
     [L|_] = get(trans_level_key()),
     L.

get_transaction_levels() ->
     case get(trans_level_key()) of
undefined ->
     exit(no_transaction);
L ->
     L
     end.

pop_transaction_level() ->
     Key = trans_level_key(),
     [H|T] = get(Key),
     put(Key, T).

trans_level_key() ->
     {?MODULE, trans_levels}.


additional_action(Type) ->
     Actions = get_actions(Type),
     do_run_actions(Actions, Type).

%% used to trigger registered commit/rollback actions
%%
additional_action(Type, Level) ->
     Key = additional_action_key(Type, Level),
     case get(Key) of
[] -> ok;
Funs when list(Funs) ->
     erase(Key),
     lists:foreach(fun(F) ->
   catch_run(F, Type)
   end, lists:reverse(Funs))
     end.

do_run_actions([{Key, Funs}|T], Type) ->
     erase(Key),
     lists:foreach(fun(F) ->
   catch_run(F, Type)
   end, lists:reverse(Funs)),
     do_run_actions(T, Type);
do_run_actions([], Type) ->
     ok.

catch_run(F, Type) ->
     case catch F() of
{'EXIT', Reason} ->
     error_logger:error_report([{?MODULE, caught_exception},
        {additional_action, Type},
        {'EXIT', Reason}]),
     ok;
_ ->
     ok
     end.


%% Clean up outer transaction -- and all its inner transactions.
cleanup_transaction() ->
     Actions = get_actions(),
     [erase(Key) || {Key, _} <- Actions],
     erase(trans_level_key()).

%% Clean up inner transaction
cleanup_transaction(Level) ->
     Actions = get_all_actions(Level),
     [erase(Key) || {Key, _} <- Actions],
     pop_transaction_level().


get_actions() ->
     [{K, V} || {K = {?MODULE,additional,_,_}, V} <- get()].

get_actions(Type) ->
     [{K, V} || {K = {?MODULE,additional,T,_}, V} <- get(),
                T == Type].

get_all_actions(Level) ->
     [{K, V} || {K = {?MODULE,additional,_,L}, V} <- get(),
                L == Level].

















Den 2005-08-30 16:24:07 skrev Claes Wikstom <klacke@REDACTED>:

> Hakan Mattsson wrote:
>
>>  Yet another solution is to have a front-end which stores all
>> updates into a persistent queue, and a back-end which reads
>> operations from the queue and performs updates of the
>> primary Mnesia database. The persistent queue should also be
>> processed on the secondary site, but there you would be more
>> free to choose the timepoint for when the database updates
>> are performed. Using this method it would be much easier to
>> recover from a communication or site failure than with the
>> other two methods.
>
>
> This is pretty close to what we've done.
>
> We have wrapped mnesia:transaction in yet another
> function of ours, foo:transaction()
>
>
> as in: foo.erl
>
> transaction(Fun, As) ->
>      F =
>      fun() ->
> 	    Res = apply(Fun, As),
> 	    case mnesia:get_activity_id() of
> 		{_, _, Ts} ->
> 		    if Ts#tidstore.level == 1 ->
> 			    Store = Ts#tidstore.store,
> 			    ok = log_transaction(Store, R),
> 			    Res;
> 		       true ->
> 			    Res
> 		    end;
> 		_ ->
> 		    Res
> 	    end
>      end,
>      mnesia:transaction(F).
>
>
>
> log_transaction(TS, Ref) ->
>
>      Bin = term_to_binary(ets:tab2list(TS)),
>
> 	... send this transaction (The Bin) to some log
> 	outside of the system.
> 	The Bin can then be replayed there
>
>
>
> /klacke
>
>
>
>



-- 
Ulf Wiger



More information about the erlang-questions mailing list