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