%%--------------------------------------------------------------------
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1999-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% File : ETraP_Server_impl.erl
%% Purpose :
%%----------------------------------------------------------------------
%% GENERAL CODE COMMENTS:
%% ######################
%% TypeChecking incoming arguments:
%% --------------------------------
%% We allow the user to configure the system so that external calls
%% (not CosTransactions calls) may be typechecked or not when calling
%% for example 'replay_completion'. With typecheck the user will get
%% instant feedback. But since 'is_a' add quiet a lot extra overhead
%% if the object is located on a remote ORB. Hence, it is up to the
%% user to decide; speed vs. "safety".
%%
%% Log behavior
%% ------------
%% Log files are created in the current directory, which is why the
%% application requires read/write rights for current directory. The
%% file name looks like:
%% "oe_nonode@nohost_subc_1429872479809947099_438" (the two last parts are
%% erlang:system_time() and erlang:unique_integer([positive]))
%% It is equal to what the object is started as, i.e., {regname, {global, X}}.
%%
%% If the application is unable to read the log it will exit and the
%% supervisor definitions (found in ETraP_Common.hrl) determines how
%% many times we will retry. If it's impossible to read the log it's
%% considered as a disaster, i.e., user intervention is needed.
%%
%% If an Object is unreachable when a Coordinator is trying to inform
%% of the true outcome of the transaction the application will retry N
%% times with T seconds wait in between. If it's still impossible to
%% reach the object it's considered as a disaster, i.e., user
%% intervention is needed.
%%
%%----------------------------------------------------------------------
-module('ETraP_Server_impl').
%%--------------- INCLUDES -----------------------------------
-include_lib("orber/include/corba.hrl").
%% Local
-include_lib("cosTransactions/src/ETraP_Common.hrl").
-include_lib("cosTransactions/include/CosTransactions.hrl").
%%--------------- IMPORTS-------------------------------------
-import('ETraP_Common', [try_timeout/1]).
%%--------------- EXPORTS-------------------------------------
%%--------------- Inherit from CosTransactions::Resource ----
-export([prepare/2,
rollback/2,
commit/2,
commit_one_phase/2,
forget/2]).
%%--------------- Inherit from CosTransactions::Control -----
-export([get_terminator/2,
get_coordinator/2]).
%%----- Inherit from CosTransactions::RecoveryCoordinator ---
-export([replay_completion/3]).
%%--------------- Inherit from CosTransactions::Coordinator -
-export([create_subtransaction/2,
get_txcontext/2,
get_transaction_name/2,
get_parent_status/2,
get_status/2,
get_top_level_status/2,
hash_top_level_tran/2,
hash_transaction/2,
is_ancestor_transaction/3,
is_descendant_transaction/3,
is_related_transaction/3,
is_same_transaction/3,
is_top_level_transaction/2,
register_resource/3,
register_subtran_aware/3,
register_synchronization/3,
rollback_only/2]).
%%--------- Inherit from CosTransactions::Synchronization ---
%-export([before_completion/2,
% after_completion/3]).
%%--------------- gen_server specific ------------------------
-export([init/1, terminate/2]).
-export([handle_call/3, handle_cast/2, handle_info/2, code_change/3]).
%%--------------- LOCAL DATA ---------------------------------
-record(exc,
{rollback = false,
mixed = false,
hazard = false,
unprepared = false,
commit = false}).
%%--------------- LOCAL DEFINITIONS --------------------------
%%--------------- MISC MACROS --------------------------------
-define(etr_log(Log, Data), etrap_logmgr:log_safe(Log, Data)).
-define(etr_read(Log, Cursor), etrap_logmgr:get_next(Log, Cursor)).
-record(coord,
{status, %% Status of the transaction.
members = [], %% List of registred resources.
votedCommit = [], %% List of the ones that voted commit.
raisedHeuristic = [], %% The members which raised an Heur. exc.
subAw = [], %% Resorces which want to be informed of outcome.
sync = [],
exc = void,
self,
etsR}).
%% Selectors
-define(etr_get_status(L), L#coord.status).
-define(etr_get_members(L), lists:reverse(L#coord.members)).
-define(etr_get_vc(L), lists:reverse(L#coord.votedCommit)).
-define(etr_get_raisedH(L), lists:reverse(L#coord.raisedHeuristic)).
-define(etr_get_exc(L), L#coord.exc).
-define(etr_get_subAw(L), lists:reverse(L#coord.subAw)).
-define(etr_get_sync(L), lists:reverse(L#coord.sync)).
-define(etr_get_self(L), L#coord.self).
-define(etr_get_etsR(L), L#coord.etsR).
-define(etr_get_init(Env), #coord{}).
-define(etr_get_exc_init(), #exc{}).
%% Modifiers
-define(etr_set_status(L, D), L#coord{status = D}).
-define(etr_set_members(L, D), L#coord{members = D}).
-define(etr_add_member(L, D), L#coord{members = [D|L#coord.members]}).
-define(etr_set_vc(L, D), L#coord{votedCommit = D}).
-define(etr_add_vc(L, D), L#coord{votedCommit = [D|L#coord.votedCommit]}).
-define(etr_remove_vc(L, D), L#coord{votedCommit =
lists:delete(D, ?etr_get_vc(L))}).
-define(etr_set_raisedH(L, D), L#coord{raisedHeuristic = [D]}).
-define(etr_add_raisedH(L, D), L#coord{raisedHeuristic =
[D|L#coord.raisedHeuristic]}).
-define(etr_remove_raisedH(L, D), L#coord{raisedHeuristic =
lists:delete(D, ?etr_get_raisedH(L))}).
-define(etr_set_exc(L, D), L#coord{exc = D}).
-define(etr_set_subAw(L, D), L#coord{subAw = [D]}).
-define(etr_add_subAw(L, D), L#coord{subAw = [D|L#coord.subAw]}).
-define(etr_remove_subAw(L, D), L#coord{subAw =
lists:delete(D,?etr_get_subAw(L))}).
-define(etr_set_sync(L, D), L#coord{sync = [D]}).
-define(etr_add_sync(L, D), L#coord{sync = [D|L#coord.sync]}).
-define(etr_remove_sync(L, D), L#coord{sync = lists:delete(D,?etr_get_sync(L))}).
-define(etr_set_self(L, D), L#coord{self = D}).
-define(etr_set_etsR(L, D), L#coord{etsR = D}).
%%------------------------------------------------------------
%% function : init, terminate
%% Arguments:
%% Returns :
%% Effect : Functions demanded by the module ic.
%%------------------------------------------------------------
init(Env) ->
process_flag(trap_exit,true),
case catch start_object(Env) of
{'EXIT', Reason} ->
%% Happens when, for example, we encounter an
%% error when reading from the log file.
{stop, Reason};
{'EXCEPTION', E} ->
self() ! {suicide, self()},
corba:raise(E);
Other ->
Other
end.
terminate(Reason, {Env, _Local}) ->
?debug_print("STOP ~p ~p~n", [?tr_get_etrap(Env), Reason]),
case Reason of
normal ->
%% normal termination. Transaction completed.
etrap_logmgr:stop(?tr_get_etrap(Env)),
file:delete(?tr_get_etrap(Env)),
ok;
_ ->
?tr_error_msg("Object(~p) terminated abnormal.~n",[?tr_get_etrap(Env)]),
ok
end.
%%------------------------------------------------------------
%% function : handle_call, handle_cast, handle_info, code_change
%% Arguments:
%% Returns :
%% Effect : Functions demanded by the gen_server module.
%%------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_call(_,_, State) ->
{noreply, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info(Info, {Env, Local}) ->
?debug_print("ETraP_Server:handle_info(~p)~n", [Info]),
Pid = self(),
case Info of
timeout ->
?tr_error_msg("Object( ~p ) timeout. Rolling back.~n",
[?tr_get_etrap(Env)]),
{stop, normal, {Env, Local}};
{suicide, Pid} ->
{stop, normal, {Env, Local}};
_->
{noreply, {Env, Local}}
end.
%%--------------- Inherit from CosTransactions::Control -----
%%-----------------------------------------------------------%
%% function : get_terminator
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : a Terminator object reference.
%% Effect : Supports operations for termination of a transaction
%%------------------------------------------------------------
get_terminator(Self, {Env, Local}) ->
%% Only allows the root-coordinator to export the termonator.
%% The reason for this is that only the root-coordinator is allowed
%% to initiate termination of a transaction. This is however possible
%% to change and add restictions elsewhere, i.e. to verify if the
%% commit or rollback call is ok.
case catch ?tr_get_parents(Env) of
[] -> % No parents, it's a root-coordinator.
% Create terminators environment.
TEnv = ?tr_set_etrap(Env, Self),
T = ?tr_start_child(?SUP_TERMINATOR(TEnv)),
{reply, T, {Env, Local}, ?tr_get_timeout(TEnv)};
_ ->
corba:raise(?tr_unavailable)
end.
%%-----------------------------------------------------------%
%% function : get_coordinator
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : a Coordinator object reference. The OMG specification
%% states that a object reference must be returned.
%% Effect : Supports operations needed by resources to participate
%% in the transaction.
%%------------------------------------------------------------
get_coordinator(Self, State) ->
{reply, Self, State}.
%%----- Inherit from CosTransactions::RecoveryCoordinator ---
%%-----------------------------------------------------------%
%% function : replay_completion
%% Arguments:
%% Returns : Status
%% Effect : Provides a hint to the Coordinator that the commit
%% or rollback operations have not been performed on
%% the resource.
%%------------------------------------------------------------
replay_completion(_Self, {Env, Local}, Resource) ->
type_check(?tr_get_typeCheck(Env), ?tr_Resource,
"RecoveryCoordinator:replay_completion", Resource),
case ?etr_get_status(Local) of
'StatusActive' ->
corba:raise(?tr_unprepared);
Status ->
case lists:any(?tr_IS_MEMBER(Resource), ?etr_get_members(Local)) of
true ->
{reply, Status, {Env, Local}};
_ ->
corba:raise(#'NO_PERMISSION'{completion_status=?COMPLETED_YES})
end
end.
%%--------------- Inherit from CosTransactions::Resource ----
%%-----------------------------------------------------------%
%% function : prepare
%% Arguments:
%% Returns : a Vote
%% Effect : Is invoked to begin the two-phase-commit on the
%% resource.
%%------------------------------------------------------------
prepare(_Self, {Env, Local}) ->
%% Set status as prepared. No new Resources are allowed to register.
NewL = ?etr_set_status(Local, 'StatusPrepared'),
?eval_debug_fun({?tr_get_etrap(Env), root_delay}, Env),
case catch send_prepare(?etr_get_members(NewL),
?tr_get_alarm(Env)) of
readOnly ->
%% All voted ReadOnly, done. No need to log.
{stop, normal, 'VoteReadOnly', {Env, NewL}};
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, 'VoteReadOnly', {Env, NewL}};
% _ ->
% {reply, 'VoteReadOnly', {Env, NewL}}
% end;
{commit, VC} ->
%% All voted Commit.
NewL2 = ?etr_set_vc(NewL, VC),
case catch try_timeout(?tr_get_alarm(Env)) of
false ->
case ?etr_log(?tr_get_etrap(Env), {pre_vote, commit, NewL2}) of
ok ->
?eval_debug_fun({?tr_get_etrap(Env), prepare1}, Env),
{reply, 'VoteCommit', {Env, NewL2}};
_->
%% Cannot log. Better to be safe than sorry; do rollback.
%% However, try to log rollback.
?etr_log(?tr_get_etrap(Env),{pre_vote, rollback, NewL2}),
send_decision({Env, NewL2}, 'VoteRollback', rollback)
end;
_->
?etr_log(?tr_get_etrap(Env),
{pre_vote, rollback, NewL2}),
%% timeout, reply rollback.
send_decision({Env, NewL2}, 'VoteRollback', rollback)
end;
{rollback, VC} ->
%% Rollback vote received.
%% Send rollback to commit voters.
N2 = ?etr_set_vc(NewL, VC),
NewL2 = ?etr_set_status(N2,'StatusRolledBack'),
?etr_log(?tr_get_etrap(Env), {pre_vote, rollback, NewL2}),
send_decision({Env, NewL2}, 'VoteRollback', rollback);
{'EXCEPTION', E, VC, Obj} ->
NewL2 = case is_heuristic(E) of
true ->
N2 = ?etr_set_vc(NewL, VC),
N3 = ?etr_set_exc(N2, E),
?etr_set_raisedH(N3, Obj);
_->
?etr_set_vc(NewL, VC)
end,
?etr_log(?tr_get_etrap(Env),{pre_vote,rollback, NewL2}),
?eval_debug_fun({?tr_get_etrap(Env), prepare2}, Env),
send_decision({Env, NewL2}, {'EXCEPTION', E}, rollback);
{failed, VC} ->
NewL2 = ?etr_set_vc(NewL, VC),
?etr_log(?tr_get_etrap(Env),{pre_vote, rollback, NewL2}),
send_decision({Env, NewL2},
{'EXCEPTION', ?tr_hazard}, rollback)
end.
%%-----------------------------------------------------------%
%% function : rollback
%% Arguments: Self - the servers own objref.
%% {Env, Local} - the servers internal state.
%% Returns : ok
%% Effect : Rollback the transaction. If its status is
%% "StatusRolledBack", this is not the first
%% rollback call to this server. Might occur if
%% the parent coordinator just recoeverd from a crasch.
%% Exception: HeuristicCommit, HeuristicMixed, HeuristicHazard
%%------------------------------------------------------------
rollback(Self, {Env, Local}) ->
case ?etr_get_status(Local) of
'StatusRolledBack' ->
case ?etr_get_exc(Local) of
void ->
{stop, normal, ok, {Env, Local}};
%% Replace the reply above if allow synchronization
%% Rolled back successfullly earlier.
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, ok, {Env, Local}};
% _ ->
% {reply, ok, {Env, Local}}
% end;
E ->
%% Already rolledback with heuristic decision
corba:raise(E)
end;
'StatusPrepared' ->
NewL = ?etr_set_status(Local, 'StatusRolledBack'),
?eval_debug_fun({?tr_get_etrap(Env), rollback}, Env),
?etr_log(?tr_get_etrap(Env), rollback),
?eval_debug_fun({?tr_get_etrap(Env), rollback2}, Env),
send_decision({Env, NewL}, ok, rollback);
'StatusActive' ->
NewL = ?etr_set_status(Local, 'StatusRolledBack'),
?etr_log(?tr_get_etrap(Env), {rollback, NewL}),
send_info(?etr_get_members(NewL), 'CosTransactions_Resource', rollback),
notify_subtrAware(rollback, ?etr_get_subAw(NewL), Self),
{stop, normal, ok, {Env, NewL}}
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, ok, {NewEnv, NewL}};
% _ ->
% {reply, ok, {NewEnv, NewL}}
% end;
end.
%%-----------------------------------------------------------%
%% function : commit
%% Arguments: Self - the servers own objref.
%% {Env, Local} - the servers internal state.
%% Returns : ok
%% Effect : Commit the transaction.
%% Exception: HeuristicRollback, HeuristicMixed, HeuristicHazard,
%% NotPrepared
%%------------------------------------------------------------
commit(_Self, {Env, Local}) ->
case ?etr_get_status(Local) of
'StatusPrepared' ->
?eval_debug_fun({?tr_get_etrap(Env), commit}, Env),
NewL = ?etr_set_status(Local, 'StatusCommitted'),
?etr_log(?tr_get_etrap(Env),commit),
?eval_debug_fun({?tr_get_etrap(Env), commit2}, Env),
send_decision({Env, NewL}, ok, commit);
'StatusCommitted' ->
case ?etr_get_exc(Local) of
void ->
{stop, normal, ok, {Env, Local}};
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, ok, {Env, Local}};
% _ ->
% {reply, ok, {Env, Local}}
% end;
E->
corba:raise(E)
end;
_ ->
corba:raise(?tr_unprepared)
end.
%%-----------------------------------------------------------%
%% function : commit_one_phase
%% Arguments: Self - the servers own objref.
%% {Env, Local} - the servers internal state.
%% Returns : ok
%% Effect : Commit the transaction using one-phase commit.
%% Use ONLY when there is only one registered Resource.
%% Exception: HeuristicRollback, HeuristicMixed, HeuristicHazard,
%% TRANSACTION_ROLLEDBACK
%%------------------------------------------------------------
commit_one_phase(_Self, {Env, Local}) ->
case ?etr_get_members(Local) of
[Resource] ->
case ?etr_get_status(Local) of
'StatusActive' ->
%% Set status as prepared. No new Resources are allowed to register.
NewL = ?etr_set_status(Local, 'StatusPrepared'),
?eval_debug_fun({?tr_get_etrap(Env), onePC}, Env),
case try_timeout(?tr_get_alarm(Env)) of
false ->
case catch 'CosTransactions_Resource':prepare(Resource) of
'VoteCommit' ->
case try_timeout(?tr_get_alarm(Env)) of
false ->
send_decision({Env, NewL}, ok, commit, [Resource]);
_->
%% Timeout, rollback.
send_decision({Env, NewL},
{'EXCEPTION',
#'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
rollback, [Resource])
end;
'VoteRollback' ->
{stop, normal,
{'EXCEPTION',
#'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
{Env, NewL}};
'VoteReadOnly' ->
{stop, normal, ok, {Env, NewL}};
{'EXCEPTION', E}
when is_record(E, 'CosTransactions_HeuristicMixed') ->
{reply, {'EXCEPTION', E}, {Env, NewL}};
{'EXCEPTION', E}
when is_record(E, 'CosTransactions_HeuristicHazard') ->
{reply, {'EXCEPTION', E}, {Env, NewL}};
Other ->
?tr_error_msg("Coordinator:prepare( ~p ) failed. REASON ~p~n",
[Resource, Other]),
{stop, normal,
{'EXCEPTION',
#'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
{Env, NewL}}
end;
_->
NewL2 = ?etr_set_status(NewL, 'StatusRolledBack'),
send_info(Resource, 'CosTransactions_Resource', rollback),
{stop, normal,
{'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
{Env, NewL2}}
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(NewL2) of
% [] ->
% send_info(Resource, 'CosTransactions_Resource', rollback),
% {stop, normal,
% {'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
% {Env, NewL2}};
% _ ->
% send_info(Resource, 'CosTransactions_Resource', rollback),
% {reply,
% {'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
% {Env, NewL2}}
% end
end;
_ ->
case evaluate_status(?etr_get_status(Local)) of
commit ->
test_exc(set_exception(?etr_get_exc_init(),
?etr_get_exc(Local)),
commit, ok, {Env, Local});
_->
test_exc(set_exception(?etr_get_exc_init(),
?etr_get_exc(Local)),
rollback,
{'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
{Env, Local})
end
end;
_->
{reply, {'EXCEPTION', #'NO_PERMISSION'{completion_status=?COMPLETED_NO}},
{Env, Local}}
end.
%%-----------------------------------------------------------%
%% function : forget
%% Arguments: Self - the servers own objref.
%% State - the servers internal state.
%% Returns : ok
%% Effect : The resource can forget all knowledge about the
%% transaction. Terminate this server.
%%------------------------------------------------------------
forget(_Self, {Env, Local}) ->
?etr_log(?tr_get_etrap(Env), forget_phase),
send_forget(?etr_get_raisedH(Local), ?tr_get_etrap(Env)),
{stop, normal, ok, {Env, ?etr_set_exc(Local, void)}}.
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, ok, {Env, ?etr_set_exc(Local, void)}};
% _ ->
% {reply, ok, {Env, ?etr_set_exc(Local, void)}}
% end.
%%--------------- Inherrit from CosTransactions::Coordinator -
%%-----------------------------------------------------------%
%% function : get_status
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : Status
%% Effect : Returns the status of the transaction associated
%% with the target object.
%%------------------------------------------------------------
get_status(_Self, {Env, Local}) ->
{reply, ?etr_get_status(Local), {Env, Local}}.
%%-----------------------------------------------------------%
%% function : get_parent_status
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : Status
%% Effect : Returns the status of the parent transaction
%% associated with the target object. If top-level
%% transaction equal to get_status.
%%------------------------------------------------------------
get_parent_status(_Self, {Env, Local}) ->
case catch ?tr_get_parents(Env) of
[] ->
{reply, ?etr_get_status(Local), {Env, Local}};
[Parent|_] ->
case catch 'CosTransactions_Coordinator':get_status(Parent) of
{'EXCEPTION', _E} ->
corba:raise(?tr_unavailable);
{'EXIT', _} ->
corba:raise(?tr_unavailable);
Status ->
{reply, Status, {Env, Local}}
end
end.
%%-----------------------------------------------------------%
%% function : get_top_level_status
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : Status
%% Effect : Returns the status of the top-level transaction
%% associated with the target object. If top-level
%% transaction equal to get_status.
%%------------------------------------------------------------
get_top_level_status(_Self, {Env, Local}) ->
case catch ?tr_get_parents(Env) of
[] ->
{reply, ?etr_get_status(Local), {Env, Local}};
Ancestrors ->
case catch 'CosTransactions_Coordinator':get_status(lists:last(Ancestrors)) of
{'EXCEPTION', _E} ->
corba:raise(?tr_unavailable);
{'EXIT', _} ->
corba:raise(?tr_unavailable);
Status ->
{reply, Status, {Env, Local}}
end
end.
%%-----------------------------------------------------------%
%% function : is_same_transaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Coordinator object reference
%% Returns : boolean
%% Effect :
%%------------------------------------------------------------
is_same_transaction(Self, {Env, Local}, Coordinator) ->
type_check(?tr_get_typeCheck(Env), ?tr_Coordinator,
"Coordinator:is_same_transaction", Coordinator),
{reply, corba_object:is_equivalent(Self, Coordinator), {Env, Local}}.
%%------------------------------------------------------------
%% function : is_related_transaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Coordinator object reference
%% Returns : boolean
%% Effect :
%%------------------------------------------------------------
is_related_transaction(_Self, {_Env, _Local}, _Coordinator) ->
corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_YES}).
% type_check(?tr_get_typeCheck(Env), ?tr_Coordinator,
% "Coordinator:is_related_transaction", Coordinator),
% {reply, false, {Env, Local}}.
%%------------------------------------------------------------
%% function : is_ancestor_transaction
%% Coordinator object reference
%% Returns : boolean
%% Effect :
%%------------------------------------------------------------
is_ancestor_transaction(_Self, {_Env, _Local}, _Coordinator) ->
corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_YES}).
% type_check(?tr_get_typeCheck(Env), ?tr_Coordinator,
% "Coordinator:is_ancestor_transaction", Coordinator),
% {reply, false, {Env, Local}}.
%%-----------------------------------------------------------%
%% function : is_descendant_transaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Coordinator object reference
%% Returns : boolean
%% Effect :
%%------------------------------------------------------------
is_descendant_transaction(Self, {Env, Local}, Coordinator) ->
type_check(?tr_get_typeCheck(Env), ?tr_Coordinator,
"Coordinator:is_descendant_transaction", Coordinator),
{reply,
lists:any(?tr_IS_MEMBER(Coordinator), [Self|?tr_get_parents(Env)]),
{Env, Local}}.
%%-----------------------------------------------------------%
%% function : is_top_level_transaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : boolean
%% Effect :
%%------------------------------------------------------------
is_top_level_transaction(_Self, {Env, Local}) ->
case catch ?tr_get_parents(Env) of
[] ->
{reply, true, {Env, Local}};
_ ->
{reply, false, {Env, Local}}
end.
%%-----------------------------------------------------------%
%% function : hash_transaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : hash code
%% Effect : Returns a hash code for the transaction associated
%% with the target object.
%%------------------------------------------------------------
hash_transaction(Self, {Env, Local}) ->
{reply, corba_object:hash(Self, ?tr_get_hashMax(Env)), {Env, Local}}.
%%-----------------------------------------------------------%
%% function : hash_top_level_tran
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : hash code
%% Effect : Returns a hash code for the top-level transaction
%% associated with the target object. Equals
%% hash_transaction if it's a top-level transaction.
%%------------------------------------------------------------
hash_top_level_tran(Self, {Env, Local}) ->
case ?tr_get_parents(Env) of
[] ->
{reply,
corba_object:hash(Self, ?tr_get_hashMax(Env)),
{Env, Local}};
Ancestrors ->
case catch corba_object:hash(lists:last(Ancestrors),
?tr_get_hashMax(Env)) of
{'EXCEPTION', _E} ->
corba:raise(?tr_unavailable);
Hash ->
{reply, Hash, {Env, Local}}
end
end.
%%-----------------------------------------------------------%
%% function : register_resource
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Resource object reference
%% Returns : RecoveryCoordinator (can be used during recovery)
%% Effect : Registers the specified resource as as participant
%% in the transaction associated with the target object.
%% Exception: Inactive - Is prepared or terminated.
%%------------------------------------------------------------
register_resource(Self, {Env, Local}, Resource) ->
type_check(?tr_get_typeCheck(Env), ?tr_Resource,
"Coordinator:register_resource", Resource),
case ?etr_get_status(Local) of
'StatusActive' -> % ok to register the Resource.
NewLocal = ?etr_add_member(Local, Resource),
RecoveryCoord = corba:create_subobject_key(Self, ?tr_get_etrap(Env)),
{reply, RecoveryCoord, {Env, NewLocal}, ?tr_get_timeout(Env)};
_-> % Not active anymore. New members not ok.
corba:raise(?tr_inactive)
end.
%%-----------------------------------------------------------%
%% function : register_subtran_aware
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% SubTransactionAwareResource object reference
%% Returns : -
%% Effect : Registers the specified object such that it
%% will be notified when the subtransaction has
%% commited or rolled back.
%%------------------------------------------------------------
register_subtran_aware(Self, {Env, Local}, SubTrAwareResource) ->
case ?tr_get_parents(Env) of
[] ->
corba:raise(?tr_NotSubtr);
_->
type_check(?tr_get_typeCheck(Env), ?tr_SubtransactionAwareResource,
"Coordinator:register_subtran_aware", SubTrAwareResource),
NewL = ?etr_add_subAw(Local, SubTrAwareResource),
{reply, ok, {Env, ?etr_set_self(NewL, Self)},
?tr_get_timeout(Env)}
end.
%%-----------------------------------------------------------%
%% function : register_synchronization
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Synchronization
%% Returns : -
%% Effect :
%%------------------------------------------------------------
register_synchronization(_Self, {_Env, _Local}, _Synchronization) ->
corba:raise(#'CosTransactions_SynchronizationUnavailable'{}).
%register_synchronization(Self, {Env, Local}, Synchronization) ->
% type_check(?tr_get_typeCheck(Env), ?tr_Synchronization,
% "Coordinator:register_synchronization", Synchronization),
% case ?etr_get_status(Local) of
% 'StatusActive' ->
% case catch ?tr_get_parents(Env) of
% [] ->
% {reply, ok, {Env, ?etr_add_sync(Local, Synchronization)},
% ?tr_get_timeout(Env)};
% [Parent|_] ->
% case catch 'ETraP_Server':register_synchronization(Parent, Self) of
% {'EXCEPTION', E} ->
% corba:raise(E);
% ok ->
% {reply, ok, {Env, ?etr_add_sync(Local, Synchronization)},
% ?tr_get_timeout(Env)};
% What ->
% corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_MAYBE})
% end
% end;
% _ ->
% corba:raise(?tr_inactive)
% end.
%%-----------------------------------------------------------%
%% function : rollback_only
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : -
%% Effect : The transaction associated with the target object
%% is modified so that rollback IS the result.
%%------------------------------------------------------------
rollback_only(Self, {Env, Local}) ->
case ?etr_get_status(Local) of
'StatusActive' ->
NewL = ?etr_set_status(Local, 'StatusRolledBack'),
NewEnv = ?tr_set_rollback(Env, true),
?etr_log(?tr_get_etrap(Env),{rollback, NewL}),
send_info(?etr_get_members(NewL), 'CosTransactions_Resource', rollback),
notify_subtrAware(rollback, ?etr_get_subAw(NewL), Self),
{stop, normal, ok, {NewEnv, NewL}};
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, ok, {NewEnv, NewL}};
% _ ->
% {reply, ok, {NewEnv, NewL}}
% end;
_ ->
corba:raise(?tr_inactive)
end.
%%-----------------------------------------------------------%
%% function : get_transaction_name
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : string - which describes the transaction associated
%% with the target object.
%% Effect : Intended for debugging.
%%------------------------------------------------------------
get_transaction_name(_Self, {Env, Local}) ->
{reply, ?tr_get_etrap(Env), {Env, Local}}.
%%-----------------------------------------------------------%
%% function : create_subtransaction
%% Arguments: Self - its own object reference.
%% State - Gen-Server State
%% Returns : A control object if subtransactions are allowed,
%% otherwise an exception is raised.
%% Effect : A new subtransaction is created whos parent is
%% the transaction associated with the target object.
%% Exception: SubtransactionUnavailabe - no support for nested
%% transactions.
%% Inactive - already been prepared.
%%------------------------------------------------------------
create_subtransaction(Self, {Env, Local}) ->
case ?etr_get_status(Local) of
'StatusActive' ->
case ?tr_get_subTraOK(Env) of
true ->
ETraPName = 'ETraP_Common':create_name("subc"),
Tname = 'ETraP_Common':create_name("subt"),
%% Create context for the new object.
State = ?tr_create_context(ETraPName, Tname,
?tr_get_typeCheck(Env),
?tr_get_hashMax(Env),
?tr_get_subTraOK(Env),
?tr_get_maxR(Env),
?tr_get_maxW(Env)),
State2 = ?tr_add_parent(State, Self),
State3 = ?tr_set_alarm(State2, ?tr_get_alarm(Env)),
State4 = ?tr_set_timeout(State3, ?tr_get_timeout(Env)),
Control = ?tr_start_child(?SUP_ETRAP(State4)),
%% Set the SubCoordinator object reference and register it as participant.
SubCoord = 'CosTransactions_Control':get_coordinator(Control),
NewLocal = ?etr_add_member(Local, SubCoord),
{reply, Control, {Env, NewLocal}, ?tr_get_timeout(Env)};
_ ->
%% subtransactions not allowed, raise exception.
corba:raise(?tr_subunavailable)
end;
_->
corba:raise(?tr_inactive)
end.
%%-----------------------------------------------------------%
%% function : get_txcontext
%% Arguments:
%% Returns : PropagationContext
%% Effect :
%%------------------------------------------------------------
get_txcontext(_Self, {_Env, _Local}) ->
corba:raise(#'CosTransactions_Unavailable'{}).
%get_txcontext(Self, {Env, Local}) ->
% Otid = #'CosTransactions_otid_t'{formatID=0,
% bqual_length=0,
% tid=[corba_object:hash(Self,
% ?tr_get_hashMax(Env))]},
% TrIDs = create_TransIdentities(?tr_get_parents(Env), Env, [], Otid),
% C=case ?tr_get_parents(Env) of
% [] ->
% #'CosTransactions_TransIdentity'{coord=Self,
% term=?tr_get_terminator(Env),
% otid=Otid};
% _->
% #'CosTransactions_TransIdentity'{coord=Self,
% term=?tr_NIL_OBJ_REF,
% otid=Otid}
% end,
% case ?tr_get_timeout(Env) of
% infinity ->
% #'CosTransactions_PropagationContext'{timeout=0,
% current= C,
% parents=TrIDs};
% T ->
% #'CosTransactions_PropagationContext'{timeout=T/1000,
% current= C,
% parents=TrIDs}
% end.
%create_TransIdentities([], _, Parents, _) -> Parents;
%create_TransIdentities([Phead|Ptail], Env, Parents, Otid) ->
% NO=Otid#'CosTransactions_TransIdentity'{otid=
% corba_object:hash(Phead,
% ?tr_get_hashMax(Env))},
% create_TransIdentities([Phead|Ptail], Env, Parents++
% [#'CosTransactions_TransIdentity'{coord=Phead,
% term=?tr_NIL_OBJ_REF,
% otid=NO}],
% Otid).
%%--------- Inherit from CosTransactions::Synchronization ---
%%-----------------------------------------------------------%
%% function : before_completion
%% Arguments:
%% Returns :
%% Effect :
%%------------------------------------------------------------
%before_completion(Self, {Env, Local}) ->
% send_info(?etr_get_sync(Local),
% 'CosTransactions_Synchronization', before_completion),
% {reply, ok, {Env, Local}}.
%%-----------------------------------------------------------%
%% function : after_completion
%% Arguments:
%% Returns :
%% Effect :
%%------------------------------------------------------------
%after_completion(Self, {Env, Local}, Status) ->
% send_info(?etr_get_sync(Local), Status,
% 'CosTransactions_Synchronization', after_completion),
% {stop, normal, ok, {Env, Local}}.
%%--------------- IMPLEMENTATION SPECIFIC -------------------
%%-----------------------------------------------------------%
%% function : start_object
%% Arguments:
%% Returns : EXIT, EXCEPTION, or {ok, State}
%% Effect : used by init/1 only.
%%------------------------------------------------------------
start_object(Env)->
?put_debug_data(self, Env),
Local = ?etr_get_init(Env),
LogName = ?tr_get_etrap(Env),
case catch file:read_file_info(LogName) of
{error, enoent} ->
%% File does not exist. It's the first time. No restart.
?debug_print("ETraP_Server:init(~p)~n",[?tr_get_etrap(Env)]),
etrap_logmgr:start(LogName),
{ok,
{Env, ?etr_set_status(Local, 'StatusActive')},
?tr_get_timeout(Env)};
{error, Reason} ->
%% File exist but error occurred.
?tr_error_msg("Control (~p) Cannot open log file: ~p~n",
[LogName, Reason]),
{stop, "unable_to_open_log"};
_ ->
%% File exists, perform restart.
etrap_logmgr:start(LogName),
?debug_print("RESTART ~p~n", [?tr_get_etrap(Env)]),
prepare_restart({Env, ?etr_set_status(Local, 'StatusUnknown')},
?etr_read(?tr_get_etrap(Env), start))
end.
%%-----------------------------------------------------------%
%% function : send_prepare
%% Arguments: List of registred resources.
%% Returns : ok - equal to void
%% Effect : calls send_prepare/3, which sends a prepare call
%% to resources participating in the transaction and then collect
%% their votes. send_prepare will block until
%% it recieves a reply from the resource.
%%------------------------------------------------------------
send_prepare(RegResources, Alarm) ->
send_prepare(RegResources, [], Alarm).
% All voted ReadOnly. We are done.
send_prepare([], [], _) ->
readOnly;
% All voted commit (VC) or ReadOnly.
send_prepare([], VC, Alarm) ->
case catch try_timeout(Alarm) of
false ->
{commit, VC};
_->
{rollback, VC}
end;
send_prepare([Rhead|Rtail], VC, Alarm) ->
?debug_print("send_prepare()~n",[]),
case catch 'CosTransactions_Resource':prepare(Rhead) of
'VoteCommit' ->
case catch try_timeout(Alarm) of
false ->
_Env = ?get_debug_data(self),
?eval_debug_fun({?tr_get_etrap(_Env), send_prepare}, _Env),
send_prepare(Rtail, VC++[Rhead], Alarm);
_->
%% Timeout, rollback. However, the resource did vote
%% commit. Add it to the list.
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC++[Rhead]}
end;
'VoteRollback' ->
%% Don't care about timeout since we voted rollback.
%% A rollback received. No need for more prepare-calls.
%% See OMG 10-51, Transaction Service:v1.0
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC};
'VoteReadOnly' ->
case catch try_timeout(Alarm) of
false ->
send_prepare(Rtail, VC, Alarm);
_->
%% timeout, reply rollback.
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC}
end;
{'EXCEPTION',E} when is_record(E, 'TIMEOUT') ->
?tr_error_msg("Coordinator:prepare( ~p )~nObject unreachable.~n",
[Rhead]),
%% Since we use presumed abort we will rollback the transaction.
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC};
{'EXCEPTION',E} when is_record(E, 'TRANSIENT') ->
?tr_error_msg("Coordinator:prepare( ~p )~nObject unreachable.~n",
[Rhead]),
%% Since we use presumed abort we will rollback the transaction.
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC};
{'EXCEPTION',E} when is_record(E, 'COMM_FAILURE') ->
?tr_error_msg("Coordinator:prepare( ~p )~nObject unreachable.~n",
[Rhead]),
%% Since we use presumed abort we will rollback the transaction.
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC};
{'EXCEPTION', E} when is_record(E, 'OBJECT_NOT_EXIST') ->
?tr_error_msg("Coordinator:prepare( ~p )~nObject unreachable.~n",
[Rhead]),
send_info(Rtail, 'CosTransactions_Resource', rollback),
{rollback, VC};
{'EXCEPTION', Exc} ->
?tr_error_msg("Coordinator:prepare( ~p )~nThe Object raised exception: ~p~n",
[Rhead, Exc]),
send_info(Rtail, 'CosTransactions_Resource', rollback),
%% This can occur if a subtransaction get one or more
%% "VoteCommit" followed by a "VoteRollback".
%% The subtransaction then do a send_decision(rollback),
%% which can generate Heuristic decisions. Must rollback
%% since at least one participant voted rollback.
{'EXCEPTION', Exc, VC, Rhead};
Other ->
?tr_error_msg("Coordinator:prepare( ~p ) failed. REASON ~p~n",
[Rhead, Other]),
send_info(Rtail, 'CosTransactions_Resource', rollback),
{failed, VC}
end.
%%-----------------------------------------------------------%
%% function : type_check
%% Arguments: Bool - perform typecheck?
%% ID - Type it should be.
%% Func - Name of the function (for error_msg)
%% Obj - objectrefernce to test.
%% Returns : 'ok' or raises exception.
%% Effect :
%%------------------------------------------------------------
type_check(false, _, _, _) ->
ok;
type_check(_, ID, Func, Obj) ->
case catch corba_object:is_a(Obj,ID) of
true ->
ok;
_ ->
?tr_error_msg("~p( ~p ) Bad argument!!~n", [Func, Obj]),
corba:raise(?tr_badparam)
end.
%%-----------------------------------------------------------%
%% function : is_heuristic
%% Arguments: Exception
%% Returns : boolean
%% Effect : Returns true if the exception is a heuristic exc.
%%------------------------------------------------------------
is_heuristic(E) when is_record(E, 'CosTransactions_HeuristicMixed') -> true;
is_heuristic(E) when is_record(E, 'CosTransactions_HeuristicHazard') -> true;
is_heuristic(E) when is_record(E, 'CosTransactions_HeuristicCommit') -> true;
is_heuristic(E) when is_record(E, 'CosTransactions_HeuristicRollback') -> true;
is_heuristic(_) -> false.
%%-----------------------------------------------------------%
%% function : exception_set
%% Arguments: Genserver state
%% Returns :
%% Effect : Used when restarting.
%%------------------------------------------------------------
exception_set({Env,Local}) ->
case ?etr_get_exc(Local) of
void ->
self() ! {suicide, self()},
{ok, {Env, Local}};
_ ->
{ok, {Env, Local}}
end.
%%-----------------------------------------------------------%
%% function : set_exception
%% Arguments: Locally defined #exc{}
%% Heuristic mixed or hazard Exeption
%% Returns : Altered locally defined #exc{}
%% Effect : Set the correct tuple member to true.
%%------------------------------------------------------------
set_exception(Exc, E) when is_record(E, 'CosTransactions_HeuristicMixed') ->
Exc#exc{mixed = true};
set_exception(Exc, E) when is_record(E, 'CosTransactions_HeuristicHazard') ->
Exc#exc{hazard = true};
set_exception(Exc, _) -> Exc.
%%-----------------------------------------------------------%
%% function : send_forget
%% Arguments:
%% Returns :
%% Effect :
%%------------------------------------------------------------
send_forget([], _) -> ok;
send_forget([Rhead|Rtail], LogName) ->
?debug_print("send_forget()~n",[]),
_Env = ?get_debug_data(self),
case catch 'CosTransactions_Resource':forget(Rhead) of
ok ->
?eval_debug_fun({?tr_get_etrap(_Env), send_forget1}, _Env),
?etr_log(LogName, {forgotten, Rhead}),
?eval_debug_fun({?tr_get_etrap(_Env), send_forget2}, _Env),
send_forget(Rtail, LogName);
Other ->
?tr_error_msg("CosTransactions_Coordinator failed sending forget to ~p~nREASON: ~p~n",
[Rhead, Other]),
?eval_debug_fun({?tr_get_etrap(_Env), send_forget3}, _Env),
?etr_log(LogName, {not_forgotten, Rhead}),
?eval_debug_fun({?tr_get_etrap(_Env), send_forget4}, _Env),
send_forget(Rtail, LogName)
end.
%%-----------------------------------------------------------%
%% function : send_decision
%% Arguments: List of registred resources which vote commit.
%% Vote - the outcome of the transaction.
%% Returns : ok - equal to void
%% Effect : Inform those who voted commit of the outcome.
%% They who voted rollback already knows the outcome.
%% They who voted ReadOnly are not affected.
%%------------------------------------------------------------
%%-- Adding extra parameters
send_decision({Env, Local}, Reply, Vote) ->
send_decision({Env, Local}, Reply, ?etr_get_vc(Local), Vote, #exc{}, [], 0).
send_decision({Env, Local}, Reply, Vote, VC) ->
send_decision({Env, Local}, Reply, VC, Vote, #exc{}, [], 0).
send_decision(State, no_reply, VC, Vote, Exc) ->
send_decision(State, no_reply, VC, Vote, Exc, [], 0).
%%-- Decision sent to all members. Do not reply (used when restarting).
send_decision({Env, Local}, no_reply, [], _, #exc{mixed = true}, [], _) ->
{Env, ?etr_set_exc(Local, ?tr_mixed)};
send_decision({Env, Local}, no_reply, [], _, #exc{hazard = true}, [], _) ->
{Env, ?etr_set_exc(Local, ?tr_hazard)};
send_decision({Env, Local}, no_reply, [], _, _, [], _) ->
{Env, Local};
send_decision({Env, Local}, no_reply, [], Vote, Exc, Failed, Times) ->
case ?tr_get_maxR(Env) of
Times ->
?tr_error_msg("MAJOR ERROR, failed sending commit decision to: ~p. Tried ~p times.", [Failed,Times]),
{Env, ?etr_set_exc(Local, ?tr_hazard)};
_->
timer:sleep(?tr_get_maxW(Env)),
NewTimes = Times+1,
send_decision({Env, Local}, no_reply, Failed, Vote, Exc, [], NewTimes)
end;
%%-- end special cases.
%% Decision sent to all members. Test exceptions.
send_decision({Env, Local}, Reply, [], Vote, Exc, [], _) ->
notify_subtrAware(Vote, ?etr_get_subAw(Local), ?etr_get_self(Local)),
test_exc(Exc, Vote, Reply, {Env, Local});
%% Decision not sent to all members (one or more failed). Retry.
send_decision({Env, Local}, Reply, [], Vote, Exc, Failed, Times) ->
case ?tr_get_maxR(Env) of
Times ->
?tr_error_msg("MAJOR ERROR, failed sending commit decision to: ~p. Tried ~p times.", [Failed,Times]),
notify_subtrAware(Vote, ?etr_get_subAw(Local), ?etr_get_self(Local)),
test_exc(Exc#exc{hazard = true}, Vote, Reply, {Env, Local});
_->
NewTimes = Times+1,
timer:sleep(?tr_get_maxW(Env)),
send_decision({Env, Local}, Reply, Failed, Vote, Exc, [], NewTimes)
end;
send_decision({Env, Local}, Reply, [Rhead|Rtail], Vote, Exc, Failed, Times) ->
?debug_print("Coordinator:send_decision(~p) Try: ~p~n",[Vote, Times]),
case catch 'CosTransactions_Resource':Vote(Rhead) of
ok ->
?etr_log(?tr_get_etrap(Env),{sent, Rhead}),
send_decision({Env, Local}, Reply, Rtail, Vote, Exc, Failed, Times);
{'EXCEPTION', E} when Vote == commit andalso
is_record(E, 'CosTransactions_NotPrepared') ->
?debug_print("send_decision resource unprepared~n",[]),
case catch 'CosTransactions_Resource':prepare(Rhead) of
'VoteCommit' ->
send_decision({Env, Local}, Reply, [Rhead|Rtail], Vote, Exc, Failed, Times);
'VoteRollback' ->
send_decision({Env, Local}, Reply, Rtail, Vote, Exc#exc{mixed = true}, Failed, Times);
{'EXCEPTION', E} ->
{SetExc, NewL, DidFail} =
evaluate_answer(E, Rhead, Vote, Exc,
?tr_get_etrap(Env), Local),
send_decision({Env, NewL}, Reply, Rtail, Vote, SetExc, DidFail++Failed, Times)
end;
{'EXCEPTION', E} ->
{SetExc, NewL, DidFail} =
evaluate_answer(E, Rhead, Vote, Exc, ?tr_get_etrap(Env), Local),
?tr_error_msg("Resource:~p( ~p )~nRaised Exception: ~p~n",
[Vote, Rhead, E]),
send_decision({Env, NewL}, Reply, Rtail, Vote, SetExc, DidFail++Failed, Times);
{'EXIT', _} ->
send_decision({Env, Local}, Reply, Rtail,
Vote, Exc, [Rhead|Failed], Times);
Other ->
?tr_error_msg("Resource:~p( ~p ) failed.~nREASON: ~p~n",
[Vote, Rhead, Other]),
case catch corba_object:non_existent(Rhead) of
true when Vote == commit ->
%% Presumed rollback
send_decision({Env, Local}, Reply, Rtail, Vote,
Exc#exc{mixed = true}, Failed, Times);
true ->
%% Presumed rollback
send_decision({Env, Local}, Reply, Rtail, Vote,
Exc#exc{hazard = true}, Failed, Times);
_ ->
send_decision({Env, Local}, Reply, Rtail,
Vote, Exc, [Rhead|Failed], Times)
end
end.
%%-----------------------------------------------------------%
%% function : notify_subtrAware,
%% Arguments:
%% Returns :
%% Effect : Invoke an operation on a list of objects. We don't
%% care about return values or exceptions.
%%------------------------------------------------------------
notify_subtrAware(commit, Resources, Self) ->
send_info(Resources, Self,
'CosTransactions_SubtransactionAwareResource',
commit_subtransaction);
notify_subtrAware(_, Resources, _) ->
send_info(Resources, 'CosTransactions_SubtransactionAwareResource',
rollback_subtransaction).
%%-----------------------------------------------------------%
%% function : send_info
%% Arguments: ObjectList - List of object refernces to call.
%% M - Module
%% F - Function
%% (Arg - required arguments)
%% Returns : ok
%% Effect : A lightweight function to be used when we don't
%% "care" about the return value.
%%------------------------------------------------------------
send_info([], _, _, _) ->
ok;
send_info([Rhead|Rtail], Arg, M, F) ->
?debug_print("~p( ~p )~n",[F, Arg]),
case catch M:F(Rhead, Arg) of
{'EXIT',R} ->
?tr_error_msg("~p:~p(~p, ~p) returned {'EXIT',~p}", [M,F,Rhead,Arg,R]);
{'EXCEPTION',E} ->
?tr_error_msg("~p:~p(~p, ~p) returned {'EXCEPTION',~p}", [M,F,Rhead,Arg,E]);
_->
ok
end,
send_info(Rtail, Arg, M, F).
send_info([], _, _) ->
ok;
send_info([Rhead|Rtail], M, F) ->
?debug_print("~p( )~n",[F]),
case catch M:F(Rhead) of
{'EXIT',R} ->
?tr_error_msg("~p:~p(~p) returned {'EXIT',~p}", [M,F,Rhead,R]);
{'EXCEPTION',E} ->
?tr_error_msg("~p:~p(~p) returned {'EXCEPTION',~p}", [M,F,Rhead,E]);
_->
ok
end,
send_info(Rtail, M, F).
%%-----------------------------------------------------------%
%% function : evaluate_answer
%% Arguments:
%% Returns :
%% Effect : Check what kind of exception we received.
%%------------------------------------------------------------
evaluate_answer(E, Rhead, _Vote, Exc, Log, Local)
when is_record(E, 'CosTransactions_HeuristicMixed') ->
?etr_log(Log, {heuristic, {Rhead, E}}),
{Exc#exc{mixed = true}, ?etr_add_raisedH(Local, Rhead), []};
evaluate_answer(E, Rhead, _Vote, Exc, Log, Local)
when is_record(E, 'CosTransactions_HeuristicHazard') ->
?etr_log(Log, {heuristic, {Rhead, E}}),
{Exc#exc{hazard = true}, ?etr_add_raisedH(Local, Rhead), []};
evaluate_answer(E, Rhead, Vote, Exc, Log, Local)
when is_record(E, 'CosTransactions_HeuristicCommit') ->
case Vote of
commit ->
?etr_log(Log, {heuristic, {Rhead, E}}),
{Exc, ?etr_add_raisedH(Local, Rhead), []};
_->
?etr_log(Log, {heuristic, {Rhead, ?tr_mixed}}),
{Exc#exc{mixed = true}, ?etr_add_raisedH(Local, Rhead), []}
end;
evaluate_answer(E, Rhead, Vote, Exc, Log, Local)
when is_record(E, 'CosTransactions_HeuristicRollback')->
case Vote of
rollback ->
?etr_log(Log, {heuristic, {Rhead, ?tr_rollback}}),
{Exc, ?etr_add_raisedH(Local, Rhead), []};
_->
?etr_log(Log, {heuristic, {Rhead, ?tr_mixed}}),
{Exc#exc{mixed = true}, ?etr_add_raisedH(Local, Rhead), []}
end;
evaluate_answer(E, Rhead, Vote, Exc, Log, Local)
when Vote == commit andalso is_record(E, 'TRANSACTION_ROLLEDBACK') ->
?etr_log(Log, {heuristic, {Rhead, ?tr_mixed}}),
{Exc#exc{mixed = true}, ?etr_add_raisedH(Local, Rhead), []};
evaluate_answer(E, Rhead, Vote, Exc, Log, Local) when is_record(E, 'TIMEOUT') ->
?tr_error_msg("Coordinator:~p( ~p ) Object unreachable.~nReason: ~p~n",
[Vote, Rhead, E]),
case catch corba_object:non_existent(Rhead) of
true ->
%% Since we have presumed abort, the child will
%% assume rollback if this server do not exist any more.
?etr_log(Log, {heuristic, {Rhead, ?tr_hazard}}),
{Exc#exc{hazard = true}, Local, []};
_ ->
{Exc, Local, [Rhead]}
end;
evaluate_answer(E, Rhead, Vote, Exc, Log, Local) when is_record(E, 'TRANSIENT') ->
?tr_error_msg("Coordinator:~p( ~p ) Object unreachable.~nReason: ~p~n",
[Vote, Rhead, E]),
case catch corba_object:non_existent(Rhead) of
true ->
%% Since we have presumed abort, the child will
%% assume rollback if this server do not exist any more.
?etr_log(Log, {heuristic, {Rhead, ?tr_hazard}}),
{Exc#exc{hazard = true}, Local, []};
_ ->
{Exc, Local, [Rhead]}
end;
evaluate_answer(E, Rhead, Vote, Exc, Log, Local) when is_record(E, 'COMM_FAILURE') ->
?tr_error_msg("Coordinator:~p( ~p ) Object unreachable.~nReason: ~p~n",
[Vote, Rhead, E]),
case catch corba_object:non_existent(Rhead) of
true ->
%% Since we have presumed abort, the child will
%% assume rollback if this server do not exist any more.
?etr_log(Log, {heuristic, {Rhead, ?tr_hazard}}),
{Exc#exc{hazard = true}, Local, []};
_ ->
{Exc, Local, [Rhead]}
end;
evaluate_answer(E, Rhead, Vote, Exc, Log, Local)when is_record(E, 'OBJECT_NOT_EXIST') ->
?tr_error_msg("Coordinator:~p( ~p ) Object unreachable.~nReason: ~p~n",
[Vote, Rhead, E]),
%% Since we have presumed abort, the child will
%% assume rollback if this server do not exist any more.
?etr_log(Log, {heuristic, {Rhead, ?tr_hazard}}),
{Exc#exc{hazard = true}, Local, []};
evaluate_answer(Unknown, Rhead, Vote, Exc, _Log, Local)->
?tr_error_msg("Coordinator:~p( ~p ). Unknown reply: ~p.~n",
[Vote, Rhead, Unknown]),
{Exc, Local, []}.
%%-----------------------------------------------------------%
%% function : test_exc
%% Arguments: Exc - instance of #exc{} locally declared.
%% Vote - 'rollback' or 'commit'
%% Reply - If no exceptions this is the default reply.
%% State - genserver state
%% Returns :
%% Effect : Raise the correct exception or simply reply to
%% the genserver. NOTE that the testing for exceptions
%% differs if we are performing a rollback or commit.
%% Check if Mixed first; takes priority over Hazard.
%% HeuristicRollback and VoteCommit together give
%% HeuristicMixed
%% HeuristicCommit and VoteRollback together give
%% HeuristicMixed
%%------------------------------------------------------------
test_exc(#exc{mixed = true}, _, _, {Env, Local}) ->
{reply, {'EXCEPTION', ?tr_mixed}, {Env, ?etr_set_exc(Local, ?tr_mixed)}};
% Left out for now to avoid dialyzer warning.
%test_exc(#exc{rollback = true}, commit, _, {Env, Local}) ->
% {reply, {'EXCEPTION', ?tr_mixed}, {Env, ?etr_set_exc(Local, ?tr_mixed)}};
% Left out for now to avoid dialyzer warning.
%test_exc(#exc{commit = true}, rollback, _, {Env, Local}) ->
% {reply, {'EXCEPTION', ?tr_mixed}, {Env, ?etr_set_exc(Local, ?tr_mixed)}};
test_exc(#exc{hazard = true}, _, _, {Env, Local}) ->
{reply, {'EXCEPTION', ?tr_hazard}, {Env, ?etr_set_exc(Local, ?tr_hazard)}};
test_exc(_, _, {'EXCEPTION', E}, {Env, Local})
when is_record(E, 'TRANSACTION_ROLLEDBACK')->
{stop, normal, {'EXCEPTION', E}, {Env, Local}};
%% Replace the case above if allow synchronization
%test_exc(_, _, {'EXCEPTION', E}, {Env, Local})
% when record(E, 'TRANSACTION_ROLLEDBACK')->
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, {'EXCEPTION', E}, {Env, Local}};
% _->
% {reply, {'EXCEPTION', E}, {Env, Local}}
% end;
test_exc(_, _, {'EXCEPTION', E}, State) ->
{reply, {'EXCEPTION', E}, State};
test_exc(_, _, Reply, {Env, Local}) ->
{stop, normal, Reply, {Env, Local}}.
%% Replace the case above if allow synchronization
%test_exc(_, _, Reply, {Env, Local}) ->
% case ?etr_get_sync(Local) of
% [] ->
% {stop, normal, Reply, {Env, Local}};
% _ ->
% {reply, Reply, {Env, Local}}
% end.
%%-----------------------------------------------------------%
%% function : evaluate_status
%% Arguments:
%% Returns :
%% Effect :
%%------------------------------------------------------------
evaluate_status(Status) ->
case Status of
'StatusCommitted' -> commit;
'StatusCommitting' -> commit;
'StatusMarkedRollback' -> rollback;
'StatusRollingBack' -> rollback;
'StatusRolledBack' -> rollback;
'StatusActive' -> rollback;
'StatusPrepared' -> rollback;
'StatusUnknown' -> rollback;
'StatusNoTransaction' -> rollback;
'StatusPreparing' -> rollback;
_-> rollback
end.
%%-----------------------------------------------------------%
%% function : prepare_restart
%% Arguments:
%% Returns :
%% Effect :
%%------------------------------------------------------------
%% The file contains no data. The coordinator crashed before
%% a prepare-call was made. Presumed rollback.
prepare_restart(State, eof) ->
?debug_print("prepare_restart: eof, init~n",[]),
self() ! {suicide, self()},
{ok, State};
%% Collected all necessary votes. Do commit_restart.
prepare_restart({Env, _}, {{pre_vote, _Vote, Data}, Cursor}) ->
?debug_print("prepare_restart: pre_vote( ~p )~n",[_Vote]),
if
?tr_is_root(Env) ->
commit_restart({Env, Data},
?etr_read(?tr_get_etrap(Env), Cursor), root);
true ->
commit_restart({Env, Data},
?etr_read(?tr_get_etrap(Env), Cursor), subCoord)
end;
%% 'rollback' called without 'prepare'. This case occurs if the Coordinator
%% crashes when send_info or notify_subtrAware.
prepare_restart({Env, _}, {{rollback, NewL}, _Cursor}) ->
?debug_print("prepare_restart: pre_vote( rollback )~n",[]),
send_info(?etr_get_members(NewL), 'CosTransactions_Resource', rollback),
notify_subtrAware(rollback, ?etr_get_subAw(NewL), ?etr_get_self(NewL)),
self() ! {suicide, self()},
{ok, {Env, NewL}};
%% Something is wrong in the log.
prepare_restart(_, _) ->
?tr_error_msg("Internal log read failed:~n", []),
{stop, {error, "restart failed"}}.
%%-----------------------------------------------------------%
%% function : commit_restart
%% Arguments: Env - server context
%% Returns :
%% Effect :
%%------------------------------------------------------------
commit_restart({Env, Local}, Data, Phase) ->
Exc = set_exception(#exc{}, ?etr_get_exc(Local)),
commit_restart({Env, Local}, Data, Phase, Exc).
%% Normal case. No errors no exceptions.
commit_restart({Env, Local}, {{sent, Obj}, Cursor}, Vote, Exc) ->
?debug_print("commit_restart: sent~n",[]),
commit_restart({Env, ?etr_remove_vc(Local, Obj)},
?etr_read(?tr_get_etrap(Env), Cursor), Vote, Exc);
commit_restart({Env, Local}, {{heuristic, {Obj,E}}, Cursor}, Vote, Exc) ->
?debug_print("commit_restart: heuristic ~p~n",[E]),
NewExc = set_exception(Exc, E),
commit_restart({Env, ?etr_add_raisedH(Local, Obj)},
?etr_read(?tr_get_etrap(Env), Cursor), Vote, NewExc);
%% --- cases which only can occure once in the log ------------
%% The file contains no data. The coordinator crashed before
%% a decision was made. Causes rollback.
commit_restart({E, L}, eof, root, Exc) ->
?debug_print("commit_restart: eof init (root only)~n",[]),
{Env, Local} = send_decision({E, L}, no_reply, ?etr_get_vc(L),
rollback, Exc),
exception_set({Env, Local});
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% exception_set({Env, Local});
% SynchObjs ->
% {ok, {Env, Local}}
% end;
%% Passed the prepare_restart. Not received a commit decision from the
%% parent.
commit_restart({E, L}, eof, subCoord, Exc) ->
?debug_print("commit_restart: eof init (subcoord only)~n",[]),
case catch corba_object:non_existent(?tr_get_parent(E)) of
true ->
%% Presumed rollback.
{Env, Local} = send_decision({E, L}, no_reply, ?etr_get_vc(L),
rollback, Exc),
self() ! {suicide, self()},
{ok, {Env, Local}};
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% self() ! {suicide, self()},
% {ok, {Env, Local}};
% SynchObjs ->
% case ?tr_get_parents(Env) of
% [] ->
% send_info(SynchObjs, ?etr_get_status(Local),
% 'CosTransactions_Synchronization', after_completion);
% _->
% ok
% end,
% self() ! {suicide, self()},
% {ok, {Env, Local}}
% end;
_->
{ok, {E, L}}
end;
commit_restart({Env, Local}, eof, Vote, Exc) ->
?debug_print("commit_restart: eof VOTE: ~p~n",[Vote]),
case ?etr_get_vc(Local) of
[] ->
?debug_print("commit_restart: all sent, test exc~n",[]),
exception_set({Env, Local});
VC ->
?debug_print("commit_restart: note done. send more~n",[]),
State = send_decision({Env, Local}, no_reply, VC, Vote, Exc),
exception_set(State)
end;
%% Decision made, i.e. rollback or commit.
commit_restart({Env, Local}, {rollback, Cursor}, _Phase, Exc) ->
?debug_print("commit_restart: decided rollback~n",[]),
commit_restart({Env, ?etr_set_status(Local, 'StatusRolledBack')},
?etr_read(?tr_get_etrap(Env), Cursor), rollback, Exc);
commit_restart({Env, Local}, {commit, Cursor}, _Phase, Exc) ->
?debug_print("commit_restart: decided commit~n",[]),
commit_restart({Env, ?etr_set_status(Local, 'StatusCommitted')},
?etr_read(?tr_get_etrap(Env), Cursor), commit, Exc);
commit_restart({Env, Local}, {forget_phase, Cursor}, _, _) ->
?debug_print("commit_restart: start sending forget~n",[]),
forget_restart({Env, Local}, ?etr_read(?tr_get_etrap(Env), Cursor));
commit_restart({_Env, _Local}, _R, _, _) ->
?debug_print("RESTART FAIL: ~p~n",[_R]),
?tr_error_msg("Internal log read failed:~n", []),
exit("restart failed").
%%-----------------------------------------------------------%
%% function : forget_restart
%% Arguments: {Env, Local} - server context
%% Returns :
%% Effect :
%%------------------------------------------------------------
%% Exception logged. Test if we issued a 'forget()' to the Resource.
forget_restart({Env, Local}, eof) ->
case ?etr_get_raisedH(Local) of
[] ->
?debug_print("forget_restart: all done~n",[]);
Left ->
?debug_print("forget_restart: not done. send more~n",[]),
send_forget(Left, ?tr_get_etrap(Env))
end,
self() ! {suicide, self()},
{ok, {Env, Local}};
%% Replace the reply above if allow synchronization
% case ?etr_get_sync(Local) of
% [] ->
% self() ! {suicide, self()},
% {ok, {Env, Local}};
% SynchObjs ->
% case ?tr_get_parents(Env) of
% [] ->
% send_info(SynchObjs, ?etr_get_status(Local),
% 'CosTransactions_Synchronization', after_completion),
% self() ! {suicide, self()},
% {ok, {Env, Local}};
% _->
% {ok, {Env, Local}}
% end
% end;
forget_restart({Env, Local}, {{forgotten, Obj}, Cursor}) ->
?debug_print("forget_restart: forgotten heuristic~n",[]),
NewL = ?etr_remove_raisedH(Local, Obj),
forget_restart({Env, NewL}, ?etr_read(?tr_get_etrap(Env), Cursor));
forget_restart({Env, Local}, {{not_forgotten, Obj}, Cursor}) ->
?debug_print("forget_restart: not_forgotten~n",[]),
NewL = ?etr_remove_raisedH(Local, Obj),
send_forget([Obj], dummy),
forget_restart({Env, NewL}, ?etr_read(?tr_get_etrap(Env), Cursor)).
%%--------------- END OF MODULE ------------------------------