From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/cosTransactions/src/ETraP_Server_impl.erl | 1739 +++++++++++++++++++++++++ 1 file changed, 1739 insertions(+) create mode 100644 lib/cosTransactions/src/ETraP_Server_impl.erl (limited to 'lib/cosTransactions/src/ETraP_Server_impl.erl') diff --git a/lib/cosTransactions/src/ETraP_Server_impl.erl b/lib/cosTransactions/src/ETraP_Server_impl.erl new file mode 100644 index 0000000000..e2c5d88f9d --- /dev/null +++ b/lib/cosTransactions/src/ETraP_Server_impl.erl @@ -0,0 +1,1739 @@ +%%-------------------------------------------------------------------- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. 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_939_383117_295538" (the last part is now()) +%% 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 ------------------------------ -- cgit v1.2.3