/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
/*
* Copyright (C) 1998, 1999, 2000, 2001,
*
* Arjuna Solutions Limited,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: BasicAction.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.arjuna.coordinator;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.logging.FacilityCode;
import com.arjuna.ats.arjuna.*;
import com.arjuna.ats.arjuna.common.*;
import com.arjuna.ats.arjuna.state.*;
import com.arjuna.ats.arjuna.objectstore.ObjectStore;
import com.arjuna.ats.arjuna.utils.ThreadUtil;
import com.arjuna.ats.arjuna.utils.Utility;
import com.arjuna.ats.internal.arjuna.Header;
import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple;
import com.arjuna.ats.internal.arjuna.thread.*;
import java.util.*;
import java.io.IOException;
import com.arjuna.common.util.logging.*;
import com.arjuna.ats.arjuna.exceptions.FatalError;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
/**
* BasicAction does most of the work of an atomic action, but does not manage
* thread scoping. This is the responsibility of any derived classes.
*
* @author Mark Little (mark@arjuna.com)
* @version $Id: BasicAction.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*
*
*
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_1
* [com.arjuna.ats.arjuna.coordinator.BasicAction_1] - Action nesting
* error - deletion of action id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_2
* [com.arjuna.ats.arjuna.coordinator.BasicAction_2] - Aborting child
* {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_3
* [com.arjuna.ats.arjuna.coordinator.BasicAction_3] - Destructor of
* still running action id {0} invoked - Aborting
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_5
* [com.arjuna.ats.arjuna.coordinator.BasicAction_5] - Activate of
* atomic action with id {0} and type {1} unexpectedly failed
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_6
* [com.arjuna.ats.arjuna.coordinator.BasicAction_6] -
* BasicAction::addChildThread () action {0} adding {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_7
* [com.arjuna.ats.arjuna.coordinator.BasicAction_7] -
* BasicAction::addChildThread () action {0} adding {1} result = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_8
* [com.arjuna.ats.arjuna.coordinator.BasicAction_8] -
* BasicAction::removeChildThread () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_9
* [com.arjuna.ats.arjuna.coordinator.BasicAction_9] - \
* BasicAction::removeChildThread () action {0} removing {1} result =
* {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_10
* [com.arjuna.ats.arjuna.coordinator.BasicAction_10] -
* BasicAction::addAction () action {0} adding {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_11
* [com.arjuna.ats.arjuna.coordinator.BasicAction_11] -
* BasicAction::addChildAction () action {0} adding {1} result = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_12
* [com.arjuna.ats.arjuna.coordinator.BasicAction_12] -
* BasicAction::removeChildAction () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_13
* [com.arjuna.ats.arjuna.coordinator.BasicAction_13] -
* BasicAction::removeChildAction () action {0} removing {1} result =
* {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_14
* [com.arjuna.ats.arjuna.coordinator.BasicAction_14] -
* BasicAction::save_state - next record to pack is a \n {0} record
* ({1}) should save it? = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_15
* [com.arjuna.ats.arjuna.coordinator.BasicAction_15] - Packing a {0}
* record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_16
* [com.arjuna.ats.arjuna.coordinator.BasicAction_16] - Packing a
* NONE_RECORD
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_17
* [com.arjuna.ats.arjuna.coordinator.BasicAction_17] - HeuristicList -
* packing a {0} record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_18
* [com.arjuna.ats.arjuna.coordinator.BasicAction_18] - HeuristicList -
* packing a NONE_RECORD
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_19
* [com.arjuna.ats.arjuna.coordinator.BasicAction_19] - Packing action
* status of {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_20
* [com.arjuna.ats.arjuna.coordinator.BasicAction_20] - Unpacked a {0}
* record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_21
* [com.arjuna.ats.arjuna.coordinator.BasicAction_21] -
* BasicAction.restore_state - could not recover {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_22
* [com.arjuna.ats.arjuna.coordinator.BasicAction_22] - HeuristicList -
* Unpacked heuristic list size of {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_23
* [com.arjuna.ats.arjuna.coordinator.BasicAction_23] - HeuristicList -
* Unpacked a {0} record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_24
* [com.arjuna.ats.arjuna.coordinator.BasicAction_24] -
* BasicAction.restore_state - error unpacking action status.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_25
* [com.arjuna.ats.arjuna.coordinator.BasicAction_25] - Restored action
* status of {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_26
* [com.arjuna.ats.arjuna.coordinator.BasicAction_26] - Restored action
* type {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_27
* [com.arjuna.ats.arjuna.coordinator.BasicAction_27] - Restored
* heuristic decision of {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_28
* [com.arjuna.ats.arjuna.coordinator.BasicAction_28] -
* BasicAction.destroy called on {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_29
* [com.arjuna.ats.arjuna.coordinator.BasicAction_29] -
* BasicAction.Begin of action {0} ignored - incorrect invocation
* sequence {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_30
* [com.arjuna.ats.arjuna.coordinator.BasicAction_30] -
* BasicAction.Begin of action {0} ignored - no parent and set as
* nested action!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_31
* [com.arjuna.ats.arjuna.coordinator.BasicAction_31] -
* BasicAction.Begin of action {0} ignored - parent action {1} is not
* running: {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_32
* [com.arjuna.ats.arjuna.coordinator.BasicAction_32] - The Arjuna
* licence does not permit any further transactions to be committed!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_33
* [com.arjuna.ats.arjuna.coordinator.BasicAction_33] - End called on
* non-running atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_34
* [com.arjuna.ats.arjuna.coordinator.BasicAction_34] - End called on
* already committed atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_35
* [com.arjuna.ats.arjuna.coordinator.BasicAction_35] - End called
* illegally on atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_36
* [com.arjuna.ats.arjuna.coordinator.BasicAction_36] -
* BasicAction.End() - prepare phase of action-id {0} failed.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_37
* [com.arjuna.ats.arjuna.coordinator.BasicAction_37] - Received
* heuristic: {0} .
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_38
* [com.arjuna.ats.arjuna.coordinator.BasicAction_38] - Action Aborting
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_39
* [com.arjuna.ats.arjuna.coordinator.BasicAction_39] - Abort called on
* non-running atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_40
* [com.arjuna.ats.arjuna.coordinator.BasicAction_40] - Abort called on
* already aborted atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_41
* [com.arjuna.ats.arjuna.coordinator.BasicAction_41] - Abort called
* illegaly on atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_42
* [com.arjuna.ats.arjuna.coordinator.BasicAction_42] - BasicAction {0} -
* non-empty ( {1} ) pendingList {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_43
* [com.arjuna.ats.arjuna.coordinator.BasicAction_43] - Transaction {0}
* marked as rollback only. Will abort.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_44
* [com.arjuna.ats.arjuna.coordinator.BasicAction_44] - Cannot force
* parent to rollback - no handle!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_45
* [com.arjuna.ats.arjuna.coordinator.BasicAction_45] -
* BasicAction::prepare - creating intentions list failed for {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_46
* [com.arjuna.ats.arjuna.coordinator.BasicAction_46] -
* BasicAction::prepare - intentions list write failed for {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_47
* [com.arjuna.ats.arjuna.coordinator.BasicAction_47] - One-phase
* commit of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_48
* [com.arjuna.ats.arjuna.coordinator.BasicAction_48] -
* BasicAction.onePhaseCommit failed - no object store for atomic
* action state!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_49
* [com.arjuna.ats.arjuna.coordinator.BasicAction_49] - Prepare phase
* of nested action {0} received inconsistent outcomes.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_50
* [com.arjuna.ats.arjuna.coordinator.BasicAction_50] - Prepare phase
* of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_51
* [com.arjuna.ats.arjuna.coordinator.BasicAction_51] -
* BasicAction.doCommit for {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_52
* [com.arjuna.ats.arjuna.coordinator.BasicAction_52] - Top-level abort
* of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_53
* [com.arjuna.ats.arjuna.coordinator.BasicAction_53] - Nested abort of
* action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_54
* [com.arjuna.ats.arjuna.coordinator.BasicAction_54] - Top-level abort
* of action {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_55
* [com.arjuna.ats.arjuna.coordinator.BasicAction_55] - Nested abort of
* action {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_56
* [com.arjuna.ats.arjuna.coordinator.BasicAction_56] -
* BasicAction.checkIsCurrent {0} - terminating non-current
* transaction: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_57
* [com.arjuna.ats.arjuna.coordinator.BasicAction_57] - Commit of
* action id {0} invoked while multiple threads active within it.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_58
* [com.arjuna.ats.arjuna.coordinator.BasicAction_58] - Abort of action
* id {0} invoked while multiple threads active within it.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_59
* [com.arjuna.ats.arjuna.coordinator.BasicAction_59] - Commit of
* action id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_60
* [com.arjuna.ats.arjuna.coordinator.BasicAction_60] - Abort of action
* id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_61
* [com.arjuna.ats.arjuna.coordinator.BasicAction_61] - \tAborting
* child: {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_62
* [com.arjuna.ats.arjuna.coordinator.BasicAction_62] - \tNow aborting
* self: {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_63
* [com.arjuna.ats.arjuna.coordinator.BasicAction_63] -
* BasicAction::removeAllChildThreads () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_64
* [com.arjuna.ats.arjuna.coordinator.BasicAction_64] -
* BasicAction.updateState - Could not create ObjectState for
* failedList
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_65
* [com.arjuna.ats.arjuna.coordinator.BasicAction_65] - BasicAction.End -
* Could not write failed list
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_66
* [com.arjuna.ats.arjuna.coordinator.BasicAction_66] - Action {0} with
* parent status {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_67
* [com.arjuna.ats.arjuna.coordinator.BasicAction_67] - Running Top
* Level Action {0} from within nested action ({1})
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_68
* [com.arjuna.ats.arjuna.coordinator.BasicAction_68] - (Internal)
* BasicAction.merge - record rejected
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_69
* [com.arjuna.ats.arjuna.coordinator.BasicAction_69] - No object store
* for:
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_70
* [com.arjuna.ats.arjuna.coordinator.BasicAction_70] - Could not
* remove intentions list:
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_71
* [com.arjuna.ats.arjuna.coordinator.BasicAction_71] - Deactivation of
* atomic action with id {0} and type {1} unexpectedly failed
* @message com.arjuna.ats.arjuna.coordinator.checkedactionfactory
* [com.arjuna.ats.arjuna.coordinator.checkedactionfactory] - Failed to resolve
* CheckedActionFactory class {0}
* @message com.arjuna.ats.arjuna.coordinator.cafactoryerror
* [com.arjuna.ats.arjuna.coordinator.cafactoryerror] - Cannot continue due to
* CheckedActionFactory resolution problem with
* @message com.arjuna.ats.arjuna.coordinator.notrunning
* [com.arjuna.ats.arjuna.coordinator.notrunning] - Cannot begin new transaction as TM is disabled. Marking as rollback-only.
*/
public class BasicAction extends StateManager
{
public BasicAction ()
{
super(ObjectType.NEITHER);
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::BasicAction()");
}
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
currentStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = ActionType.NESTED;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = _checkedActionFactory.getCheckedAction(get_uid(), type());
_childThreads = null;
_childActions = null;
}
/**
* BasicAction constructor with a Uid. This constructor is for recreating an
* BasicAction, typically during crash recovery.
*/
public BasicAction (Uid objUid)
{
super(objUid, ObjectType.NEITHER);
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::BasicAction("
+ objUid + ")");
}
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
currentStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = ActionType.NESTED;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = _checkedActionFactory.getCheckedAction(get_uid(), type());
_childThreads = null;
_childActions = null;
}
/**
* BasicAction destructor. Under normal circumstances we do very little.
* However there exists the possibility that this action is being deleted
* while still running (user forgot to commit/abort) - in which case we do
* an abort for him and mark all our parents as unable to commit.
* Additionally due to scoping we may not be the current action - but in
* that case the current action must be one of our nested actions so by
* applying abort to it we should end up at ourselves!
*/
public void finalize ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.DESTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::finalize()");
}
if ((actionStatus == ActionStatus.RUNNING)
|| (actionStatus == ActionStatus.ABORT_ONLY))
{
/* If current action is one of my children there's an error */
BasicAction currentAct = BasicAction.Current();
if ((currentAct != null) && (currentAct != this))
{
/*
* Is the current action a child of this action? If so, abort
* until we get to the current action. This works even in a
* multi-threaded environment where each thread may have a
* different notion of current, since Current returns the thread
* specific current.
*/
if (currentAct.isAncestor(get_uid()))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_1", new Object[]
{ get_uid() });
}
while ((currentAct != this) && (currentAct != null))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_2", new Object[]
{ currentAct.get_uid() });
}
currentAct.Abort();
currentAct = BasicAction.Current();
}
}
}
BasicAction parentAct = parent();
/* prevent commit of parents (safety) */
while (parentAct != null)
{
parentAct.preventCommit();
parentAct = parentAct.parent();
}
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_3", new Object[]
{ get_uid() });
}
/* This will also kill any children */
Abort();
}
else
{
if (actionStatus == ActionStatus.PREPARED)
Thread.yield();
}
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentStore = null;
currentHierarchy = null;
_checkedAction = null;
if (_childThreads != null)
{
_childThreads.clear();
_childThreads = null;
}
if (_childActions != null)
{
_childActions.clear();
_childActions = null;
}
}
/**
* Return the action hierarchy for this transaction.
*/
public final synchronized ActionHierarchy getHierarchy ()
{
return currentHierarchy;
}
/**
* Force the only outcome for the transaction to be to rollback. Only
* possible if this transaction has not (or is not) terminated.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean preventCommit ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::preventCommit( "+this+")");
}
boolean res = false;
// if (lockMutex())
{
/*
* If we are active then change status. Otherwise it may be an error so check status.
*/
if (actionStatus == ActionStatus.RUNNING)
actionStatus = ActionStatus.ABORT_ONLY;
/*
* Since the reason to call this method is to make sure the transaction
* only aborts, check the status now and if it has aborted or will abort then
* we'll consider it a success.
*/
res = ((actionStatus == ActionStatus.ABORT_ONLY) || (actionStatus == ActionStatus.ABORTED) || (actionStatus == ActionStatus.ABORTING));
// unlockMutex();
}
return res;
}
/**
* @return the number of threads associated with this transaction.
*/
public final int activeThreads ()
{
if (_childThreads != null)
return _childThreads.size();
else
return 0;
}
/**
* Add a record to the atomic action. This function returns AR_ADDED if the
* record is added. AR_REJECTED if the record cannot be added because the
* action is past the prepare phase, and IGNORED otherwise.
*
* @return <code>AddOutcome</code> indicating outcome.
*/
public final synchronized int add (AbstractRecord A)
{
int result = AddOutcome.AR_REJECTED;
criticalStart();
if ((actionStatus <= ActionStatus.ABORTING)
&& ((recordBeingHandled == null) || !(recordBeingHandled.equals(A))))
{
if (pendingList == null)
pendingList = new RecordList();
result = (pendingList.insert(A) ? AddOutcome.AR_ADDED
: AddOutcome.AR_DUPLICATE);
}
criticalEnd();
return result;
}
/**
* @return the depth of the current transaction hierarchy.
*/
public final synchronized int hierarchyDepth ()
{
if (currentHierarchy != null)
return currentHierarchy.depth();
else
return 0; /* should never happen */
}
/**
* boolean function that checks whether the Uid passed as an argument is the
* Uid for an ancestor of the current atomic action.
*
* @return <code>true</code> if the parameter represents an ancestor,
* <code>false</code> otherwise.
*/
public final boolean isAncestor (Uid ancestor)
{
boolean res = false;
if (get_uid().equals(ancestor)) /* actions are their own ancestors */
res = true;
else
{
if ((parentAction != null) && (actionType != ActionType.TOP_LEVEL))
res = parentAction.isAncestor(ancestor);
}
return res;
}
/**
* @return a reference to the parent BasicAction
*/
public final BasicAction parent ()
{
if (actionType == ActionType.NESTED)
return parentAction;
else
return null;
}
public final int typeOfAction ()
{
return actionType;
}
/**
* @return the status of the BasicAction
*/
public final int status ()
{
int s = ActionStatus.INVALID;
// if (tryLockMutex())
{
s = actionStatus;
// unlockMutex();
}
return s;
}
/**
* Set up an object store and assign it to the currentStore variable.
*
* @return the object store implementation to use.
* @see com.arjuna.ats.arjuna.objectstore.ObjectStore
*/
public ObjectStore getStore ()
{
if (currentStore == null)
{
currentStore = TxControl.getStore();
}
return currentStore;
}
public final ObjectStore store ()
{
return getStore();
}
/**
* The following function returns the Uid of the top-level atomic action. If
* this is the top-level transaction then it is equivalent to calling
* get_uid().
*
* @return the top-level transaction's <code>Uid</code>.
*/
public final Uid topLevelActionUid ()
{
BasicAction root = this;
while (root.parent() != null)
root = root.parent();
return root.get_uid();
}
/**
* @return a reference to the top-level transaction. If this is the
* top-level transaction then a reference to itself will be
* returned.
*/
public final BasicAction topLevelAction ()
{
BasicAction root = this;
while (root.parent() != null)
root = root.parent();
return root;
}
/**
* Overloaded version of activate -- sets up the store, performs read_state
* followed by restore_state. The store root is <code>null</code>.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public boolean activate ()
{
return activate(null);
}
/**
* Overloaded version of activate -- sets up the store, performs read_state
* followed by restore_state. The root of the object store to use is
* specified in the <code>root</code> parameter.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public boolean activate (String root)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::activate() for action-id "
+ get_uid());
}
boolean restored = false;
// Set up store
ObjectStore aaStore = store();
if (aaStore == null)
return false;
try
{
// Read object state
InputObjectState oState = aaStore.read_committed(getSavingUid(), type());
if (oState != null)
{
synchronized (this)
{
restored = restore_state(oState, ObjectType.ANDPERSISTENT);
}
oState = null;
}
else
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_5", new Object[]
{ get_uid(), type() });
}
restored = false;
}
return restored;
}
catch (ObjectStoreException e)
{
if (tsLogger.arjLogger.isWarnEnabled())
tsLogger.arjLogger.warn(e);
return false;
}
}
/**
* This operation deactivates a persistent object. It behaves in a similar
* manner to the activate operation, but has an extra argument which defines
* whether the object's state should be committed or written as a shadow.
*
* The root of the object store is <code>null</code>. It is assumed that
* this is being called during a transaction commit.
*
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
public boolean deactivate ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::deactivate() for action-id "
+ get_uid());
}
boolean deactivated = false;
// Set up store
ObjectStore aaStore = store();
if (aaStore == null)
return false;
try
{
// Write object state
OutputObjectState oState = new OutputObjectState();
if (save_state(oState, ObjectType.ANDPERSISTENT))
{
if (aaStore.write_committed(getSavingUid(), type(), oState))
{
deactivated = true;
}
else
{
deactivated = false;
}
oState = null;
}
else
{
deactivated = false;
}
/** If we failed to deactivate then output warning * */
if (!deactivated)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_5", new Object[]
{ get_uid(), type() });
}
}
}
catch (ObjectStoreException e)
{
if (tsLogger.arjLogger.isWarnEnabled())
tsLogger.arjLogger.warn(e);
deactivated = false;
}
return deactivated;
}
/**
* Add the current thread to the list of threads associated with this
* transaction.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean addChildThread () // current thread
{
return addChildThread(Thread.currentThread());
}
/**
* Add the specified thread to the list of threads associated with this
* transaction.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean addChildThread (Thread t)
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_6", new Object[]
{ get_uid(), t });
}
if (t == null)
return false;
boolean result = false;
criticalStart();
synchronized (this)
{
if (actionStatus <= ActionStatus.ABORTING)
{
if (_childThreads == null)
_childThreads = new Hashtable();
_childThreads.put(ThreadUtil.getThreadId(t), t); // makes sure so we don't get
// duplicates
result = true;
}
}
criticalEnd();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_7", new Object[]
{ get_uid(), t, new Boolean(result) });
}
return result;
}
/*
* Can be done at any time (Is this correct?)
*/
/**
* Remove a child thread. The current thread is removed.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean removeChildThread () // current thread
{
return removeChildThread(ThreadUtil.getThreadId());
}
/**
* Remove the specified thread from the transaction.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean removeChildThread (String threadId)
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_8", new Object[]
{ get_uid(), threadId });
}
if (threadId == null)
return false;
boolean result = false;
criticalStart();
synchronized (this)
{
if (_childThreads != null)
{
_childThreads.remove(threadId);
result = true;
}
}
criticalEnd();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
if (result)
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_9", new Object[]
{ get_uid(), threadId, "true" });
else
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_9", new Object[]
{ get_uid(), threadId, "false" });
}
return result;
}
/**
* Add a new child action to the atomic action.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean addChildAction (BasicAction act)
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_10", new Object[]
{ get_uid(), ((act != null) ? act.get_uid() : Uid.nullUid()) });
}
if (act == null)
return false;
boolean result = false;
criticalStart();
synchronized (this)
{
/*
* Must be <= as we sometimes need to do processing during commit
* phase.
*/
if (actionStatus <= ActionStatus.ABORTING)
{
if (_childActions == null)
_childActions = new Hashtable();
_childActions.put(act, act);
result = true;
}
}
criticalEnd();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
if (result)
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_11", new Object[]
{ get_uid(), act.get_uid(), "true" });
else
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_11", new Object[]
{ get_uid(), act.get_uid(), "false" });
}
return result;
}
/*
* Can be done at any time (Is this correct?)
*/
/**
* Remove a child action.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public final boolean removeChildAction (BasicAction act)
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_12", new Object[]
{ get_uid(), ((act != null) ? act.get_uid() : Uid.nullUid()) });
}
if (act == null)
return false;
boolean result = false;
criticalStart();
synchronized (this)
{
if (_childActions != null)
{
_childActions.remove(act);
result = true;
}
}
criticalEnd();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
if (result)
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_13", new Object[]
{ get_uid(), act.get_uid(), "true" });
else
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_13", new Object[]
{ get_uid(), act.get_uid(), "false" });
}
return result;
}
/**
* Add the specified CheckedAction object to this transaction.
*
* @return the previous <code>CheckedAction</code>.
* @see com.arjuna.ats.arjuna.coordinator.CheckedAction
*/
public final synchronized CheckedAction setCheckedAction (CheckedAction c)
{
criticalStart();
CheckedAction toReturn = _checkedAction;
_checkedAction = c;
criticalEnd();
return toReturn;
}
/**
* @return the Uid that the transaction's intentions list will be saved
* under.
*/
public Uid getSavingUid ()
{
return get_uid();
}
/**
* Overloads Object.toString()
*/
public String toString ()
{
return new String("BasicAction: " + get_uid() + " status: "
+ ActionStatus.stringForm(actionStatus));
}
/**
* Redefined version of save_state and restore_state from StateManager.
*
* Normal operation (no crashes):
*
* BasicAction.save_state is called after a successful prepare. This causes
* and BasicAction object to be saved in the object store. This object
* contains primarily the "intentions list" of the BasicAction. After
* successfully completing phase 2 of the commit protocol, the BasicAction
* object is deleted from the store.
*
* Failure cases:
*
* If a server crashes after successfully preparing, then upon recovery the
* action must be resolved (either committed or aborted) depending upon
* whether the co-ordinating atomic action committed or aborted. Upon server
* recovery, the crash recovery mechanism detects ServerBasicAction objects
* in the object store and attempts to activate the BasicAction object of
* the co-ordinating action. If this is successful then the SAA is committed
* else aborted.
*
* If, when processing phase 2 of the commit protocol, the co-ordinator
* experiences a failure to commit from one of the records then the
* BasicAction object is NOT deleted. It is rewritten when a new state which
* contains a list of the records that failed during phase 2 commit. This
* list is called the "failedList".
*
* The crash recovery manager will detect local BasicAction objects in
* addition to SAA objects in the objectstore. An attempt will be made to
* commit these actions. If the action contained a call to a now dead
* server, this action can never be resolved and the AA object can never be
* removed. However, if the action is purely local then after the processing
* is complete the removed by crash recovery.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public boolean save_state (OutputObjectState os, int ot)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::save_state ()");
}
try
{
packHeader(os, new Header(get_uid(), Utility.getProcessUid()));
}
catch (IOException e)
{
return false;
}
/*
* In a presumed abort scenario, this routine is called: a) After a
* successful prepare - to save the intentions list. b) After a failure
* during phase 2 of commit - to overwrite the intentions list by the
* failedList.
*
* If we're using presumed nothing, then it could be called: a) Whenever
* a participant is registered.
*/
RecordList listToSave = null;
boolean res = true;
/*
* If we have a failedList then we are re-writing a BasicAction object
* after a failure during phase 2 commit
*/
if ((failedList != null) && (failedList.size() > 0))
{
listToSave = failedList;
}
else
{
listToSave = preparedList;
}
AbstractRecord first = ((listToSave != null) ? listToSave.getFront()
: null);
AbstractRecord temp = first;
boolean havePacked = ((listToSave == null) ? false : true);
while ((res) && (temp != null))
{
listToSave.putRear(temp);
/*
* First check to see if we need to call save_state. If we do then
* we must first save the record type (and enum) and then save the
* unique identity of the record (a string). The former is needed to
* determine what type of record we are restoring, while the latter
* is required to re-create the actual record.
*/
/*
* First check to see if we need to call save_state. If we do then
* we must first save the record type. This is used to determine
* which type of record to create when restoring.
*/
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
if (temp.doSave())
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_14", new Object[]
{ Integer.toString(temp.typeIs()), temp.type(), "true" });
else
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_14", new Object[]
{ Integer.toString(temp.typeIs()), temp.type(), "false" });
}
if (temp.doSave())
{
res = true;
try
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_15", new Object[]
{ Integer.toString(temp.typeIs()) });
}
os.packInt(temp.typeIs());
res = temp.save_state(os, ot);
}
catch (IOException e)
{
res = false;
}
}
temp = listToSave.getFront();
if (temp == first)
{
listToSave.putFront(temp);
temp = null;
}
}
/*
* If we only ever had a heuristic list (e.g., one-phase commit) then
* pack a record delimiter.
*/
if (res && (os.notempty() || !havePacked))
{
try
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_16");
}
os.packInt(RecordType.NONE_RECORD);
}
catch (IOException e)
{
res = false;
}
}
if (res)
{
// Now deal with anything on the heuristic list!
int hSize = ((heuristicList == null) ? 0 : heuristicList.size());
try
{
os.packInt(hSize);
}
catch (IOException e)
{
res = false;
}
if (res && (hSize > 0))
{
first = heuristicList.getFront();
temp = first;
while (res && (temp != null))
{
heuristicList.putRear(temp);
if (temp.doSave())
{
res = true;
try
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_17", new Object[]
{ Integer.toString(temp.typeIs()) });
}
os.packInt(temp.typeIs());
res = temp.save_state(os, ot);
}
catch (IOException e)
{
res = false;
}
}
temp = heuristicList.getFront();
if (temp == first)
{
heuristicList.putFront(temp);
temp = null;
}
}
if (res && os.notempty())
{
try
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_18");
}
os.packInt(RecordType.NONE_RECORD);
}
catch (IOException e)
{
res = false;
}
}
}
}
if (res && os.notempty())
{
try
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_19", new Object[]
{ ActionStatus.stringForm(actionStatus) });
}
os.packInt(actionStatus);
os.packInt(actionType); // why pack since only top-level?
os.packInt(heuristicDecision); // can we optimize?
}
catch (IOException e)
{
res = false;
}
}
return res;
}
/**
* This assumes the various lists are zero length when it is called.
*
* @return <code>true</code> if successful, <code>false</code>
* otherwise.
*/
public boolean restore_state (InputObjectState os, int ot)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::restore_state ()");
}
createPreparedLists();
boolean res = true;
int record_type = RecordType.NONE_RECORD;
int tempActionStatus = ActionStatus.INVALID;
int tempActionType = ActionType.TOP_LEVEL;
int tempHeuristicDecision = TwoPhaseOutcome.PREPARE_OK;
/*
* Unpack the prepared list. Note: This may either be a full intentions
* list or just the failedList, either way, restore it as the prepared
* list.
*/
try
{
Header hdr = new Header();
unpackHeader(os, hdr);
}
catch (IOException e)
{
return false;
}
try
{
record_type = os.unpackInt();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_20", new Object[]
{ Integer.toString(record_type) });
}
}
catch (IOException e)
{
res = false;
}
while ((res) && (record_type != RecordType.NONE_RECORD))
{
AbstractRecord record = AbstractRecord.create(record_type);
if (record == null)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_21", new Object[]
{ Integer.toString(record_type) });
}
res = false;
}
else
res = (record.restore_state(os, ot) && preparedList.insert(record));
if (res)
{
try
{
record_type = os.unpackInt();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_20", new Object[]
{ Integer.toString(record_type) });
}
}
catch (IOException e)
{
res = false;
}
}
}
// Now deal with the heuristic list!
int hSize = 0;
if (res)
{
try
{
hSize = os.unpackInt();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_22", new Object[]
{ Integer.toString(hSize) });
}
}
catch (IOException e)
{
res = false;
}
}
if (hSize > 0)
{
try
{
record_type = os.unpackInt();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_23", new Object[]
{ Integer.toString(record_type) });
}
}
catch (IOException e)
{
res = false;
}
while ((res) && (record_type != RecordType.NONE_RECORD))
{
AbstractRecord record = AbstractRecord.create(record_type);
res = (record.restore_state(os, ot) && heuristicList.insert(record));
try
{
record_type = os.unpackInt();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_23", new Object[]
{ Integer.toString(record_type) });
}
}
catch (IOException e)
{
res = false;
}
}
}
if (res)
{
try
{
tempActionStatus = os.unpackInt();
tempActionType = os.unpackInt();
tempHeuristicDecision = os.unpackInt();
}
catch (IOException e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_24");
res = false;
}
}
if (res)
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_25", new Object[]
{ ActionStatus.stringForm(tempActionStatus), Integer.toString(tempActionStatus) });
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_26", new Object[]
{ ((tempActionType == ActionType.NESTED) ? "Nested"
: "Top-level"), Integer.toString(tempActionType) });
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_27", new Object[]
{ TwoPhaseOutcome.stringForm(tempHeuristicDecision), Integer.toString(tempHeuristicDecision) });
}
actionStatus = tempActionStatus;
actionType = tempActionType;
heuristicDecision = tempHeuristicDecision;
savedIntentionList = true;
}
return res;
}
/**
* Overloads StateManager.type()
*/
public String type ()
{
return "/StateManager/BasicAction";
}
/**
* @return the thread's notion of the current transaction.
*/
public static BasicAction Current ()
{
return ThreadActionData.currentAction();
}
/**
* If heuristic outcomes are returned, by default we will not save the state
* once the forget method has been called on them (which happens as soon as
* we have received all outcomes from registered resources). By specifying
* otherwise, we will always maintain the heuristic information, which may
* prove useful for logging and off-line resolution.
*
* @return <code>true</code> if the transaction should save its heuristic
* information, <code>false</code> otherwise.
*/
public static boolean maintainHeuristics ()
{
return TxControl.maintainHeuristics;
}
/**
* Overloads <code>StateManager.destroy</code> to prevent destroy being
* called on a BasicAction. Could be a *very* bad idea!!
*
* @return <code>false</code>.
* @see com.arjuna.ats.arjuna.StateManager
*/
public boolean destroy ()
{
return true;
}
/**
* @return the list of child transactions. Currently only their ids are
* given.
* @since JTS 2.2.
*/
public final Object[] childTransactions ()
{
int size = _childActions.size();
if (size > 0)
{
Collection c = _childActions.values();
return c.toArray();
}
return null;
}
public boolean equals (java.lang.Object obj)
{
if (obj instanceof BasicAction)
{
if (((BasicAction) obj).get_uid().equals(get_uid()))
return true;
}
return false;
}
/**
* Forget any heuristics we may have received, and tell the resources which
* generated them to forget too.
*
* @return <code>true</code> if heuristic information (if any) was
* successfully forgotten, <code>false</code> otherwise.
*/
protected boolean forgetHeuristics ()
{
if ((heuristicList != null) && (heuristicList.size() > 0))
{
doForget(heuristicList);
updateState();
if (heuristicList.size() == 0)
return true;
else
return false;
}
else
return true;
}
/**
* Atomic action Begin operation. Does not change the calling thread's
* notion of the current transaction.
*
* @return <code>ActionStatus</code> indicating outcome.
*/
protected synchronized int Begin (BasicAction parentAct)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::Begin() for action-id "
+ get_uid());
}
// check to see if transaction system is enabled
if (!TxControl.isEnabled())
{
/*
* Prevent transaction from making forward progress.
*/
actionStatus = ActionStatus.ABORT_ONLY;
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.notrunning");
}
}
else
{
if (actionStatus != ActionStatus.CREATED)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_29", new Object[]
{ get_uid(), ActionStatus.stringForm(actionStatus) });
}
}
else
{
actionInitialise(parentAct);
actionStatus = ActionStatus.RUNNING;
if ((actionType != ActionType.TOP_LEVEL)
&& ((parentAct == null) || (parentAct.status() > ActionStatus.RUNNING)))
{
actionStatus = ActionStatus.ABORT_ONLY;
if (parentAct == null)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_30", new Object[]
{ get_uid() });
}
}
else
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_31", new Object[]
{ get_uid(), parentAct.get_uid(), Integer.toString(parentAct.status()) });
}
}
}
ActionManager.manager().put(this);
if (TxStats.enabled())
{
TxStats.getInstance().incrementTransactions();
if (parentAct != null)
TxStats.getInstance().incrementNestedTransactions();
}
}
}
return actionStatus;
}
/**
* End the atomic action by committing it. This invokes the prepare()
* operation. If this succeeds then the pendingList should be empty and the
* records that were formally on it will have been distributed between the
* preparedList and the readonlyList, also if the action is topLevel then
* the intention list will have been written to the object store. Then
* invoke phase2Commit and clean up the object store if necessary
*
* If prepare() fails invoke phase2Abort. In this case the pendingList may
* still contain records but phase2Abort takes care of these. Also in this
* case no intention list has been written.
*
* Does not change the calling thread's notion of the current transaction.
*
* Any heuristic outcomes will only be reported if the parameter is
* <code>true</code>.
*
* @return <code>ActionStatus</code> indicating outcome.
*/
protected synchronized int End (boolean reportHeuristics)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::End() for action-id "
+ get_uid());
}
/* Check for superfluous invocation */
if ((actionStatus != ActionStatus.RUNNING)
&& (actionStatus != ActionStatus.ABORT_ONLY))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
switch (actionStatus)
{
case ActionStatus.CREATED:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_33", new Object[]
{ get_uid() });
break;
case ActionStatus.COMMITTED:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_34", new Object[]
{ get_uid() });
break;
default:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_35", new Object[]
{ get_uid() });
break;
}
}
return actionStatus;
}
/*
* Check we are the current action. Abort parents if not true. Check we
* have not children (threads or actions).
*/
if (!checkIsCurrent() || checkChildren(true)
|| (actionStatus == ActionStatus.ABORT_ONLY))
{
return Abort();
}
if (pendingList != null)
{
/*
* If we only have a single item on the prepare list then we can try
* to commit in a single phase.
*/
if (doOnePhase())
{
onePhaseCommit(reportHeuristics);
ActionManager.manager().remove(get_uid());
}
else
{
if (prepare(reportHeuristics) == TwoPhaseOutcome.PREPARE_NOTOK)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_36", new Object[]
{ get_uid() });
}
if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_37", new Object[]
{ TwoPhaseOutcome.stringForm(heuristicDecision) });
}
}
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_38");
if (!reportHeuristics && TxControl.asyncCommit
&& (parentAction == null))
{
AsyncCommit.create(this, false);
}
else
phase2Abort(reportHeuristics); /* first phase failed */
}
else
{
if (!reportHeuristics && TxControl.asyncCommit
&& (parentAction == null))
{
AsyncCommit.create(this, true);
}
else
phase2Commit(reportHeuristics); /* first phase succeeded */
}
}
}
else
{
ActionManager.manager().remove(get_uid());
actionStatus = ActionStatus.COMMITTED;
}
boolean returnCurrentStatus = false;
if (reportHeuristics || (!reportHeuristics && !TxControl.asyncCommit))
returnCurrentStatus = true;
if (returnCurrentStatus)
{
if (reportHeuristics)
{
switch (heuristicDecision)
{
case TwoPhaseOutcome.PREPARE_OK:
case TwoPhaseOutcome.FINISH_OK:
break;
case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
return ActionStatus.H_ROLLBACK;
case TwoPhaseOutcome.HEURISTIC_COMMIT:
return ActionStatus.H_COMMIT;
case TwoPhaseOutcome.HEURISTIC_MIXED:
return ActionStatus.H_MIXED;
case TwoPhaseOutcome.HEURISTIC_HAZARD:
default:
return ActionStatus.H_HAZARD;
}
}
/*
* If we have a heuristic decision then we only report it
* if required. Otherwise we return committed as per OTS rules.
*/
switch (actionStatus)
{
case ActionStatus.H_COMMIT:
case ActionStatus.H_ROLLBACK:
case ActionStatus.H_HAZARD:
case ActionStatus.H_MIXED:
if (!reportHeuristics)
return ActionStatus.COMMITTED;
default:
return actionStatus;
}
}
else
return ActionStatus.COMMITTING; // if asynchronous then fake it.
}
/**
* This is the user callable abort operation. It is invoked prior to the
* start of two-phase commit and hence only processes records in the
* pendingList (the other lists should be empty).
*
* Does not change the calling thread's notion of the current transaction.
*
* @return <code>ActionStatus</code> indicating outcome.
*/
protected synchronized int Abort ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::Abort() for action-id "
+ get_uid());
}
/* Check for superfluous invocation */
if ((actionStatus != ActionStatus.RUNNING)
&& (actionStatus != ActionStatus.ABORT_ONLY)
&& (actionStatus != ActionStatus.COMMITTING))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
switch (actionStatus)
{
case ActionStatus.CREATED:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_39", new Object[]
{ get_uid() });
break;
case ActionStatus.ABORTED:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_40", new Object[]
{ get_uid() });
break;
default:
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_41", new Object[]
{ get_uid() });
break;
}
}
return actionStatus;
}
/*
* Check we are the current action. Abort parents if not true. Some
* implementations may want to override this.
*/
checkIsCurrent();
/*
* Check we have no children (threads or actions).
*/
checkChildren(false);
if (pendingList != null)
{
actionStatus = ActionStatus.ABORTING;
while (pendingList.size() > 0)
doAbort(pendingList, false); // turn off heuristics reporting
/*
* In case we get here because an End has failed. In this case we
* still need to tell the heuristic resources to forget their
* decision.
*/
forgetHeuristics();
}
ActionManager.manager().remove(get_uid());
actionStatus = ActionStatus.ABORTED;
if (TxStats.enabled())
TxStats.getInstance().incrementAbortedTransactions();
return actionStatus;
}
/**
* Create a transaction of the specified type.
*/
protected BasicAction (int at)
{
super(ObjectType.NEITHER);
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
currentStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = at;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = _checkedActionFactory.getCheckedAction(get_uid(), type());
_childThreads = null;
_childActions = null;
}
/**
* Recreate the specified transaction. Used for crash recovery purposes.
*/
protected BasicAction (Uid u, int at)
{
super(u, ObjectType.NEITHER);
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
currentStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = at;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = _checkedActionFactory.getCheckedAction(get_uid(), type());
_childThreads = null;
_childActions = null;
}
/**
* Defines the start of a critical region by setting the critical flag. If
* the signal handler is called the class variable abortAndExit is set. The
* value of this variable is checked in the corresponding operation to end
* the critical region.
*/
protected final void criticalStart ()
{
// _lock.lock();
}
/**
* Defines the end of a critical region by resetting the critical flag. If
* the signal handler is called the class variable abortAndExit is set. The
* value of this variable is checked when ending the critical region.
*/
protected final void criticalEnd ()
{
// _lock.unlock();
}
/**
* Cleanup phase for actions. If an action is in the PREPARED state when a
* terminate signal is delivered (ie the coordinator node has crashed) then
* we need to cleanup. This is essentially the same as phase2Abort but we
* call cleanup ops rather than abort ops and let the records take care of
* appropriate cleanup.
*
* The pendingList is processed because it may not be empty - since
* prepare() stops processing the list at the first PREPARE_NOTOK result.
*
* The read_only list is processed to ensure that actions are aborted
* immediately and any servers killed at that point since they need not hang
* around. This contrasts with commit where readonlyList entries are simply
* merged with the parent list or discarded
*/
protected final synchronized void phase2Cleanup ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::phase2Cleanup() for action-id "
+ get_uid());
}
criticalStart();
actionStatus = ActionStatus.CLEANUP;
while ((preparedList != null) && (preparedList.size() > 0))
doCleanup(preparedList);
while ((readonlyList != null) && (readonlyList.size() > 0))
doCleanup(readonlyList);
while ((pendingList != null) && (pendingList.size() > 0))
doCleanup(pendingList);
criticalEnd();
}
/**
* Second phase of the two-phase commit protocol for committing actions.
* This operation first invokes the doCommit operation on the preparedList.
* This ensures that the appropriate commit operation is performed on each
* entry which is then either deleted (top_level) or merged into the
* parent's pendingList.
*
* Processing of the readonlyList is different in that if the action is
* top_level then all records in the readonlyList are deleted without
* further processing. If nested the records must be merged. This is an
* optimisation to avoid unnecessary processing.
*
* Note that at this point the pendingList SHOULD be empty due to the prior
* invocation of prepare().
*/
protected synchronized final void phase2Commit (boolean reportHeuristics)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::phase2Commit() for action-id "
+ get_uid());
}
if ((pendingList != null) && (pendingList.size() > 0))
{
int size = ((pendingList == null) ? 0 : pendingList.size());
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_42", new Object[]
{ get_uid(), Integer.toString(size), pendingList });
}
phase2Abort(reportHeuristics);
}
else
{
criticalStart();
actionStatus = ActionStatus.COMMITTING;
/*
* If we get a heuristic during commit then we continue to commit
* since we may have already told some records to commit. We could
* optimise this if the first record raises the heuristic by
* aborting (or going with the heuristic decision).
*/
doCommit(preparedList, reportHeuristics); /*
* process the
* preparedList
*/
/*
* Now check any heuristic decision. If we received one then we may
* have to raise HEURISTIC_MIXED since we will have committed some
* resources, whereas others may have aborted.
*/
if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
{
/*
* Heuristic decision matched the actual outcome!
*/
if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT)
heuristicDecision = TwoPhaseOutcome.FINISH_OK;
}
/* The readonlyList requires special attention */
if ((readonlyList != null) && (readonlyList.size() > 0))
{
if (!TxControl.readonlyOptimisation)
{
if (readonlyList != null)
doCommit(readonlyList, reportHeuristics);
}
// now still process the list.
while (((recordBeingHandled = readonlyList.getFront()) != null))
{
if ((actionType == ActionType.NESTED)
&& (recordBeingHandled.propagateOnCommit()))
{
merge(recordBeingHandled);
}
else
{
recordBeingHandled = null;
}
}
}
forgetHeuristics();
actionStatus = ActionStatus.COMMITTED;
updateState();
ActionManager.manager().remove(get_uid());
criticalEnd();
// ok count this as a commit unless we got a heuristic rollback in which case phase2Abort
// will have been called and will already have counted it as an abort
if (TxStats.enabled()) {
if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_ROLLBACK) {
TxStats.getInstance().incrementCommittedTransactions();
}
}
}
}
/**
* Second phase of the two phase commit protocol for aborting actions.
* Actions are aborted by invoking the doAbort operation on the
* preparedList, the readonlyList, and the pendingList.
*
* The pendingList is processed because it may not be empty - since
* prepare() stops processing the list at the first PREPARE_NOTOK result.
*
* By default, records that responsed PREPARE_READONLY will not be contacted
* during second-phase abort, just as they are not during second-phase
* commit. This can be overridden at runtime using the READONLY_OPTIMISATION
* variable.
*/
protected synchronized final void phase2Abort (boolean reportHeuristics)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::phase2Abort() for action-id "
+ get_uid());
}
criticalStart();
actionStatus = ActionStatus.ABORTING;
if (preparedList != null)
doAbort(preparedList, reportHeuristics);
if (!TxControl.readonlyOptimisation)
{
if (readonlyList != null)
doAbort(readonlyList, reportHeuristics);
}
if (pendingList != null)
doAbort(pendingList, reportHeuristics);
/*
* Check heuristic decision, and try to make it match outcome.
*/
if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
{
if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
heuristicDecision = TwoPhaseOutcome.FINISH_OK;
}
forgetHeuristics();
actionStatus = abortStatus();
updateState(); // we may end up saving more than the heuristic list
// here!
ActionManager.manager().remove(get_uid());
criticalEnd();
/*
* To get to this stage we had to try to commit, which means that we're
* rolling back because of a resource problem.
*/
if (TxStats.enabled()) {
TxStats.getInstance().incrementResourceRollbacks();
TxStats.getInstance().incrementAbortedTransactions();
}
}
/**
* Phase one of a two phase commit protocol. This function returns the
* ouctome of the prepare operation. If all goes well it will be PREPARE_OK,
* if not PREPARE_NOTOK. The value PREPARE_READONLY may also be returned if
* all the records indicate that they are readonly records. Such records do
* not take part in the second phase commit processing.
*
* @return <code>TwoPhaseOutcome</code> indicating outcome.
*/
protected synchronized final int prepare (boolean reportHeuristics)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::prepare () for action-id "
+ get_uid());
}
boolean commitAllowed = (actionStatus != ActionStatus.ABORT_ONLY);
actionStatus = ActionStatus.PREPARING;
/* If we cannot commit - say the prepare failed */
if (!commitAllowed)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_43", new Object[]
{ get_uid() });
}
actionStatus = ActionStatus.PREPARED;
return TwoPhaseOutcome.PREPARE_NOTOK;
}
/*
* Make sure the object store is set up for a top-level atomic action.
*/
if (actionType == ActionType.TOP_LEVEL)
{
if (store() == null)
{
actionStatus = ActionStatus.ABORT_ONLY;
return TwoPhaseOutcome.PREPARE_NOTOK;
}
}
criticalStart();
createPreparedLists();
/*
* Here is the start of the hard work. Walk down the pendingList
* invoking the appropriate prepare operation. If it succeeds put the
* record on either the preparedList or the read_only list and continue
* until the pendingList is exhausted.
*
* If prepare fails on any record stop processing immediately and put
* the offending record back on the pendingList
*/
int p = TwoPhaseOutcome.PREPARE_OK;
/*
* If asynchronous prepare, then spawn a separate thread to handle each
* entry in the intentions list. Could have some configurable option to
* allow more limited number of threads to divide up the intentions
* list.
*/
if ((actionType == ActionType.TOP_LEVEL) && (TxControl.asyncPrepare))
{
int numberOfThreads = ((pendingList != null) ? pendingList.size()
: 0);
Thread[] threads = new Thread[numberOfThreads];
int i;
/*
* First create them in a suspended way, so that we can purge the
* list before it is added to (in the event of failures!)
*/
for (i = 0; i < numberOfThreads; i++)
{
threads[i] = AsyncPrepare.create(this, reportHeuristics, pendingList.getFront());
}
/*
* Now start the threads running.
*/
for (i = 0; i < numberOfThreads; i++)
{
threads[i].start();
Thread.yield();
}
/*
* If one of these threads fails (PREPARE_NOTOK) do we terminate the
* others or simply let them finish? Currently we wait and let them
* all terminate regardless.
*/
/*
* Now synchronise with the threads.
*/
for (int j = 0; j < numberOfThreads; j++)
{
while (threads[j].isAlive())
{
try
{
threads[j].join();
}
catch (Exception e)
{
if (tsLogger.arjLogger.isWarnEnabled())
{
tsLogger.arjLogger.warn(e);
}
p = TwoPhaseOutcome.PREPARE_NOTOK;
}
}
/*
* Only set the outcome if the current value is PREPARE_OK.
*/
if (p == TwoPhaseOutcome.PREPARE_OK)
p = ((AsyncPrepare) threads[j]).outcome();
threads[j] = null;
}
}
else
{
boolean ok = true;
do
{
try
{
p = doPrepare(reportHeuristics);
/*
* Stop as soon as we get a failure return code.
*/
if ((p != TwoPhaseOutcome.PREPARE_OK)
&& (p != TwoPhaseOutcome.PREPARE_READONLY))
{
ok = false;
}
}
catch (IndexOutOfBoundsException e)
{
// end of list!
ok = false;
}
} while (ok);
}
if ((p != TwoPhaseOutcome.PREPARE_OK)
&& (p != TwoPhaseOutcome.PREPARE_READONLY))
{
if ((actionType == ActionType.NESTED)
&& ((preparedList.size() > 0) && (p == TwoPhaseOutcome.ONE_PHASE_ERROR)))
{
/*
* For the OTS we must merge those records told to commit with
* the parent, as the rollback invocation must come from that
* since they have already been told this transaction has
* committed!
*/
AbstractRecord tmpRec = preparedList.getFront();
while (tmpRec != null)
{
merge(tmpRec);
tmpRec = preparedList.getFront();
}
if (parentAction != null)
parentAction.preventCommit();
else
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_44");
}
}
criticalEnd();
return TwoPhaseOutcome.PREPARE_NOTOK;
}
/*
* Now work out whether there is any state to save. Since we should be
* single threaded once again, there is no need to protect the lists
* with a synchronization.
*/
/*
* Could do this as we traverse the lists above, but would need some
* compound class for return values.
*/
boolean stateToSave = false;
RecordListIterator iter = new RecordListIterator(preparedList);
/*
* First check the prepared list.
*/
while (((recordBeingHandled = iter.iterate()) != null))
{
if (!stateToSave)
stateToSave = recordBeingHandled.doSave();
if (stateToSave)
break;
}
iter = null;
if (!stateToSave)
{
iter = new RecordListIterator(heuristicList);
/*
* Now check the heuristic list.
*/
while (((recordBeingHandled = heuristicList.getFront()) != null))
{
if (!stateToSave)
stateToSave = recordBeingHandled.doSave();
if (stateToSave)
break;
}
iter = null;
}
/*
* The actual state we want to write depends upon whether or not we are
* in charge of the transaction outcome:
*
* (i) if we are a root transaction, or an interposed transaction which
* received a commit_one_phase call, then we have complete control over
* what the transaction outcome will be. So, we will always try to
* commit, and can set the state to committing.
*
* (ii) if we are an interposed transaction and it receives a complete
* two-phase protocol, then the root is in control. So, we set the state
* to prepared.
*
* (iii) nested transactions never write state, so the state is set to
* prepared anyway.
*/
if (actionType == ActionType.TOP_LEVEL)
actionStatus = preparedStatus();
else
actionStatus = ActionStatus.PREPARED;
/*
* If we are here then everything went okay so save the intention list
* in the ObjectStore in case of a node crash providing that its not
* empty
*/
if ((actionType == ActionType.TOP_LEVEL) && (stateToSave)
&& ((preparedList.size() > 0) || (heuristicList.size() > 0)))
{
/* Only do this if we have some records worth saving! */
Uid u = getSavingUid();
String tn = type();
OutputObjectState state = new OutputObjectState(u, tn);
if (!save_state(state, ObjectType.ANDPERSISTENT))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_45", new Object[]
{ get_uid() });
}
criticalEnd();
return TwoPhaseOutcome.PREPARE_NOTOK;
}
if (state.notempty())
{
try
{
if (!currentStore.write_committed(u, tn, state))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_46", new Object[]
{ get_uid() });
}
criticalEnd();
return TwoPhaseOutcome.PREPARE_NOTOK;
}
else
savedIntentionList = true;
}
catch (ObjectStoreException e)
{
criticalEnd();
return TwoPhaseOutcome.PREPARE_NOTOK;
}
}
}
criticalEnd();
if ((preparedList.size() == 0) && (readonlyList.size() >= 0))
return TwoPhaseOutcome.PREPARE_READONLY;
else
return TwoPhaseOutcome.PREPARE_OK;
}
/**
* There is only one record on the intentions list. Only called from
* synchronized methods. Don't bother about creating separate threads here!
*/
protected void onePhaseCommit (boolean reportHeuristics)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::onePhaseCommit() for action-id "
+ get_uid());
}
/* Are we forced to abort? */
if (actionStatus == ActionStatus.ABORT_ONLY)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_43", new Object[]
{ get_uid() });
}
Abort();
return;
}
actionStatus = ActionStatus.COMMITTING;
criticalStart();
if ((heuristicList == null) && reportHeuristics)
heuristicList = new RecordList();
if (failedList == null)
failedList = new RecordList();
/*
* Since it is one-phase, the outcome from the record is the outcome of
* the transaction. Therefore, we don't need to save much intermediary
* transaction state - only heuristics in the case of interposition.
*/
boolean stateToSave = false;
recordBeingHandled = pendingList.getFront();
int p = ((actionType == ActionType.TOP_LEVEL) ? recordBeingHandled.topLevelOnePhaseCommit()
: recordBeingHandled.nestedOnePhaseCommit());
if ((p == TwoPhaseOutcome.FINISH_OK)
|| (p == TwoPhaseOutcome.PREPARE_READONLY))
{
if ((actionType == ActionType.NESTED)
&& recordBeingHandled.propagateOnCommit())
{
merge(recordBeingHandled);
}
else
{
recordBeingHandled = null;
}
actionStatus = ActionStatus.COMMITTED;
}
else
{
if ((p == TwoPhaseOutcome.FINISH_ERROR) || (p == TwoPhaseOutcome.ONE_PHASE_ERROR))
{
/*
* If ONE_PHASE_ERROR then the resource has rolled back. Otherwise we
* don't know and will ask recovery to keep trying. We differentiate
* this kind of failure from a heuristic failure so that we can allow
* recovery to retry the commit attempt periodically.
*/
if (p == TwoPhaseOutcome.FINISH_ERROR)
{
/*
* We still add to the failed list because this may not mean
* that the transaction has aborted.
*/
if (!failedList.insert(recordBeingHandled))
recordBeingHandled = null;
else
{
if (!stateToSave)
stateToSave = recordBeingHandled.doSave();
}
/*
* There's been a problem and we need to retry later. Assume
* transaction has committed until we have further information.
* This also ensures that recovery will kick in periodically.
*/
actionStatus = ActionStatus.COMMITTED;
}
else
actionStatus = ActionStatus.ABORTED;
}
else
{
/*
* Heuristic decision!!
*/
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_47", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(p) });
}
if (reportHeuristics)
{
updateHeuristic(p, true);
if (!heuristicList.insert(recordBeingHandled))
recordBeingHandled = null;
else
{
if (!stateToSave)
stateToSave = recordBeingHandled.doSave();
}
}
if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
{
/*
* Signal that the action outcome is the same as the
* heuristic decision.
*/
heuristicDecision = TwoPhaseOutcome.PREPARE_OK; // means no
// heuristic
// was
// raised.
actionStatus = ActionStatus.ABORTED;
}
else if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT)
{
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
actionStatus = ActionStatus.COMMITTED;
}
else
actionStatus = ActionStatus.H_HAZARD; // can't really say
// (could have
// aborted)
}
}
if (actionType == ActionType.TOP_LEVEL)
{
if (stateToSave && ((heuristicList.size() > 0) || (failedList.size() > 0)))
{
if (store() == null)
{
if (tsLogger.arjLoggerI18N.isFatalEnabled())
{
tsLogger.arjLoggerI18N.fatal("com.arjuna.ats.arjuna.coordinator.BasicAction_48");
}
throw new com.arjuna.ats.arjuna.exceptions.FatalError(
tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.coordinator.BasicAction_69")
+ get_uid());
}
updateState();
}
}
forgetHeuristics();
ActionManager.manager().remove(get_uid());
criticalEnd();
if (TxStats.enabled()) {
if (actionStatus == ActionStatus.ABORTED) {
TxStats.getInstance().incrementAbortedTransactions();
} else {
TxStats.getInstance().incrementCommittedTransactions();
}
}
}
/**
* @return the current heuristic decision. Each time a heuristic outcome is
* received, we need to merge it with any previous outcome to
* determine what the overall heuristic decision is (e.g., a
* heuristic rollback followed by a heuristic commit means the
* overall decision is heuristic mixed.)
*/
protected final synchronized int getHeuristicDecision ()
{
return heuristicDecision;
}
/**
* Add the specified abstract record to the transaction. Does not do any of
* the runtime checking of BasicAction.add, so should be used with care.
* Currently used by crash recovery.
*/
protected final synchronized void addRecord (AbstractRecord A)
{
preparedList.insert(A);
}
/**
* @return the transaction's prepared status.
*
* @since JTS 2.0.
*/
protected int preparedStatus ()
{
if (actionType == ActionType.TOP_LEVEL)
return ActionStatus.COMMITTING;
else
return ActionStatus.PREPARED;
}
protected int abortStatus ()
{
return ActionStatus.ABORTED;
}
protected int commitStatus ()
{
return ActionStatus.COMMITTED;
}
/*
* Package visibility.
*/
/*
* The single-threaded version of doPrepare. If we do not use asynchronous
* prepare, then we don't need to lock the RecordLists - only one thread can
* access them anyway!
*/
/*
* If we do not use asynchronous prepare, then we don't need to lock the
* RecordLists - only one thread can access them anyway! However, if
* asynchronous prepare is being used then we need to synchronize on them.
* The 'lock' parameter (default false) indicates whether we should try to
* synchronize the RecordList or not.
*/
protected int doPrepare (boolean reportHeuristics)
throws IndexOutOfBoundsException
{
/*
* Here is the start of the hard work. Walk down the pendingList
* invoking the appropriate prepare operation. If it succeeds put the
* record on either the preparedList or the read_only list and continue
* until the pendingList is exhausted.
*
* If prepare fails on any record stop processing immediately and put
* the offending record back on the pendingList.
*/
int p = TwoPhaseOutcome.PREPARE_NOTOK;
do
{
AbstractRecord rec = ((pendingList != null) ? pendingList.getFront()
: null);
/*
* March down the pendingList and pass the head of the list to the
* main work routine until either we run out of elements, or one of
* them fails.
*/
if (rec != null)
{
/*
* If a failure occurs then the record will be put back on to
* the pending list. Otherwise it is moved to another list or
* dropped if readonly.
*/
p = doPrepare(reportHeuristics, rec);
/*
* If failure then it's back on the pending list by now. Move on
* or terminate?
*
* Move on if a cohesion, but terminate if an atom!!
*/
}
else
throw new IndexOutOfBoundsException();
} while ((p == TwoPhaseOutcome.PREPARE_OK)
|| (p == TwoPhaseOutcome.PREPARE_READONLY));
return p;
}
/*
* The multi-threaded version of doPrepare. Each thread was given the record
* it should process when it was created so that if a failure occurs we can
* put it back onto the pendingList at the right place. It also cuts down on
* the amount of synchronisation we must do.
*/
protected int doPrepare (boolean reportHeuristics, AbstractRecord theRecord)
throws IndexOutOfBoundsException
{
/*
* Here is the start of the hard work. Walk down the pendingList
* invoking the appropriate prepare operation. If it succeeds put the
* record on either the preparedList or the read_only list and continue
* until the pendingList is exhausted.
*
* If prepare fails on any record stop processing immediately and put
* the offending record back on the pendingList.
*/
int p = TwoPhaseOutcome.PREPARE_NOTOK;
AbstractRecord record = theRecord;
if (record != null)
{
p = ((actionType == ActionType.TOP_LEVEL) ? record.topLevelPrepare()
: record.nestedPrepare());
if (p == TwoPhaseOutcome.PREPARE_OK)
{
record = insertRecord(preparedList, record);
}
else
{
if (p == TwoPhaseOutcome.PREPARE_READONLY)
{
record = insertRecord(readonlyList, record);
}
else
{
if ((p == TwoPhaseOutcome.PREPARE_NOTOK)
|| (p == TwoPhaseOutcome.ONE_PHASE_ERROR)
|| (!reportHeuristics))
{
/*
* If we are a subtransaction and this is an OTS
* resource then we may be in trouble: we may have
* already told other records to commit.
*/
if (actionType == ActionType.NESTED)
{
if ((preparedList.size() > 0)
&& (p == TwoPhaseOutcome.ONE_PHASE_ERROR))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_49", new Object[]
{ get_uid() });
}
/*
* Force parent to rollback. If this is not the
* desired result then we may need to check some
* environment variable (either here or in the
* OTS) and act accordingly. If we check in the
* OTS then we need to return something other
* than PREPARE_NOTOK.
*/
/*
* For the OTS we must merge those records told
* to commit with the parent, as the rollback
* invocation must come from that since they
* have already been told this transaction has
* committed!
*
* However, since we may be multi-threaded
* (asynchronous prepare) we don't do the
* merging yet. Wait until all threads have
* terminated and then do it.
*
* Therefore, can't force parent to rollback
* state at present, or merge will fail.
*/
}
}
/*
* Prepare on this record failed - we are in trouble.
* Add the record back onto the pendingList and return.
*/
record = insertRecord(pendingList, record);
record = null;
actionStatus = ActionStatus.PREPARED;
return p;
}
else
{
/*
* Heuristic decision!!
*/
/*
* Only report if request to do so.
*/
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_50", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(p) });
}
if (reportHeuristics)
updateHeuristic(p, false);
/*
* Don't add to the prepared list. We process heuristics
* separately during phase 2. The processing of records
* will not be in the same order as during phase 1, but
* does this matter for heuristic decisions? If so, then
* we need to modify RecordList so that records can
* appear on multiple lists at the same time.
*/
record = insertRecord(heuristicList, record);
/*
* If we have had a heuristic decision, then attempt to
* make the action outcome the same. If we have a
* conflict, then we will abort.
*/
if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_COMMIT)
{
actionStatus = ActionStatus.PREPARED;
return TwoPhaseOutcome.PREPARE_NOTOK;
}
else
{
/*
* Heuristic commit, which is ok since we want to
* commit anyway! So, ignore it (but remember the
* resource so we can tell it to forget later.)
*/
}
}
}
}
}
else
// no more entries in pendingList
throw new IndexOutOfBoundsException();
return p;
}
/**
* Walk down a record list extracting records and calling the appropriate
* commit function. Discard or merge records as appropriate
*/
protected int doCommit (RecordList rl, boolean reportHeuristics)
{
if ((rl != null) && (rl.size() > 0))
{
AbstractRecord rec;
boolean pastFirstParticipant = false;
while (((rec = rl.getFront()) != null))
{
int outcome = doCommit(reportHeuristics, rec);
/*
* Check the outcome and if we have a heuristic rollback try to
* rollback everything else in the list *if* we have not already
* committed something. That way we make the outcome for all
* participants the same as the first (rollback) and don't get a
* heuristic!
*/
switch (outcome)
{
case TwoPhaseOutcome.FINISH_OK:
case TwoPhaseOutcome.HEURISTIC_COMMIT:
pastFirstParticipant = true;
break;
case TwoPhaseOutcome.HEURISTIC_MIXED:
case TwoPhaseOutcome.HEURISTIC_HAZARD:
default:
/*
* Do nothing and continue to commit everything else. We've
* got this far as errors have caused problems, but we gain
* nothing by now rolling back some participants. This could
* cause further heuristics!
*/
pastFirstParticipant = true;
break;
case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
{
/*
* A heuristic decision of commit means that we have got
* past the first entry in the list. So there is no going
* back now!
*/
if (pastFirstParticipant)
break;
else
{
/*
* Remember the heuristic decision so we can restore it
* after rolling back. Otherwise we can't return the
* right value from commit.
*/
pastFirstParticipant = true;
int oldDecision = heuristicDecision;
phase2Abort(reportHeuristics);
heuristicDecision = oldDecision;
}
}
break;
}
}
}
return TwoPhaseOutcome.FINISH_OK;
}
protected int doCommit (boolean reportHeuristics, AbstractRecord record)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::doCommit ("
+ record + ")");
}
/*
* To get heuristics right, as soon as we manage to commit the first
* record we set the heuristic to HEURISTIC_COMMIT. Then, if any other
* heuristics are raised we can manage the final outcome correctly.
*/
int ok = TwoPhaseOutcome.FINISH_ERROR;
recordBeingHandled = record;
if (recordBeingHandled != null)
{
if (actionType == ActionType.TOP_LEVEL)
{
if ((ok = recordBeingHandled.topLevelCommit()) == TwoPhaseOutcome.FINISH_OK)
{
/*
* Record successfully committed, we can delete it now.
*/
recordBeingHandled = null;
updateHeuristic(TwoPhaseOutcome.FINISH_OK, true); // must
// remember
// that
// something
// has
// committed
}
else
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_51", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(ok), RecordType.typeToClass(recordBeingHandled.typeIs()) });
}
if ((reportHeuristics)
&& ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
|| (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
|| (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD)))
{
updateHeuristic(ok, true);
heuristicList.insert(recordBeingHandled);
}
else
{
if (ok == TwoPhaseOutcome.NOT_PREPARED)
{
/*
* If this is the first resource then rollback,
* otherwise promote to HEURISTIC_HAZARD, but don't
* add to heuristicList.
*/
updateHeuristic(TwoPhaseOutcome.HEURISTIC_HAZARD, true);
}
else
{
/*
* The commit failed. Add this record to the failed
* list to indicate this. Covers statuses like FAILED_ERROR.
*/
failedList.insert(recordBeingHandled);
}
}
}
}
else
{
/*
* Thankfully nested actions cannot raise heuristics!
*/
ok = recordBeingHandled.nestedCommit();
if (recordBeingHandled.propagateOnCommit())
{
merge(recordBeingHandled);
}
else
{
recordBeingHandled = null;
}
}
if (ok != TwoPhaseOutcome.FINISH_OK)
{
/* Preserve error messages */
}
}
return ok;
}
/*
* Walk down a record list extracting records and calling the appropriate
* abort function. Discard records when done.
*/
protected int doAbort (RecordList list_toprocess, boolean reportHeuristics)
{
if ((list_toprocess != null) && (list_toprocess.size() > 0))
{
while ((recordBeingHandled = list_toprocess.getFront()) != null)
{
doAbort(reportHeuristics, recordBeingHandled);
}
}
return TwoPhaseOutcome.FINISH_OK;
}
protected int doAbort (boolean reportHeuristics, AbstractRecord record)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::doAbort ("
+ record + ")");
}
int ok = TwoPhaseOutcome.FINISH_OK;
recordBeingHandled = record;
if (recordBeingHandled != null)
{
if (actionType == ActionType.TOP_LEVEL)
ok = recordBeingHandled.topLevelAbort();
else
ok = recordBeingHandled.nestedAbort();
if ((actionType != ActionType.TOP_LEVEL)
&& (recordBeingHandled.propagateOnAbort()))
{
merge(recordBeingHandled);
}
else
{
if (ok == TwoPhaseOutcome.FINISH_OK)
{
updateHeuristic(TwoPhaseOutcome.FINISH_OK, false); // remember
// that
// something
// aborted
// ok
}
else
{
if ((reportHeuristics)
&& ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
|| (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
|| (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD)))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
if (actionType == ActionType.TOP_LEVEL)
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_52", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(ok) });
else
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_53", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(ok) });
}
updateHeuristic(ok, false);
heuristicList.insert(recordBeingHandled);
}
else
{
if (ok != TwoPhaseOutcome.FINISH_OK)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
if (actionType == ActionType.TOP_LEVEL)
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_54", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(ok), RecordType.typeToClass(recordBeingHandled.typeIs()) });
else
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_55", new Object[]
{ get_uid(), TwoPhaseOutcome.stringForm(ok), RecordType.typeToClass(recordBeingHandled.typeIs()) });
}
}
}
}
/*
* Don't need a canDelete as in the C++ version since Java's
* garbage collection will deal with things for us.
*/
recordBeingHandled = null;
}
}
return ok;
}
protected AbstractRecord insertRecord (RecordList reclist, AbstractRecord record)
{
boolean lock = TxControl.asyncPrepare;
if (lock)
{
synchronized (reclist)
{
if (!reclist.insert(record))
record = null;
}
}
else
{
if (!reclist.insert(record))
record = null;
}
return record;
}
/**
* Do we want to check that a transaction can only be terminated by a thread
* that has it as its current transaction? The base class has this check
* enabled (i.e., we check), but some implementations may wish to override
* this.
*
* @return <code>false</code> to disable checking.
*/
protected boolean checkForCurrent ()
{
return false;
}
/*
* If we get a single heuristic then we will always rollback during prepare.
*
* Getting a heuristic during commit is slightly different, since some
* resources may have already committed, changing the type of heuristic we
* may need to throw. However, once we get to commit we know that it will be
* the final outcome. So, as soon as a single resource commits successfully,
* we can take it as a HEURISTIC_COMMIT. We will forget a HEURISTIC_COMMIT
* outcome at the end anyway.
*/
protected final synchronized void updateHeuristic (int p, boolean commit)
{
/*
* Some resource has prepared/committed ok, so we need to remember this
* in case we get a future heuristic.
*/
if (p == TwoPhaseOutcome.FINISH_OK)
{
if (commit)
{
if (heuristicDecision == TwoPhaseOutcome.PREPARE_OK)
p = TwoPhaseOutcome.HEURISTIC_COMMIT;
if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
}
else
{
if (heuristicDecision == TwoPhaseOutcome.PREPARE_OK)
p = TwoPhaseOutcome.HEURISTIC_ROLLBACK;
if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT)
heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
}
// leave HAZARD and MIXED alone
}
/*
* Is this the first heuristic? Always give HEURISTIC_MIXED priority,
* but if we have no heuristic and we get a HEURISTIC_HAZARD then go
* with that until something better comes along!
*/
/*
* Have we already been given a conflicting heuristic? If so, raise the
* decision to the next heuristic level.
*/
switch (heuristicDecision)
{
case TwoPhaseOutcome.PREPARE_OK:
if ((p != TwoPhaseOutcome.PREPARE_OK)
&& (p != TwoPhaseOutcome.FINISH_OK)) // first heuristic
// outcome.
heuristicDecision = p;
break;
case TwoPhaseOutcome.HEURISTIC_COMMIT:
if ((p == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
|| (p == TwoPhaseOutcome.HEURISTIC_MIXED))
heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
else
{
if (p == TwoPhaseOutcome.HEURISTIC_HAZARD)
heuristicDecision = TwoPhaseOutcome.HEURISTIC_HAZARD;
}
break;
case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
if ((p == TwoPhaseOutcome.HEURISTIC_COMMIT)
|| (p == TwoPhaseOutcome.HEURISTIC_MIXED))
heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
else
{
if (p == TwoPhaseOutcome.HEURISTIC_HAZARD)
heuristicDecision = TwoPhaseOutcome.HEURISTIC_HAZARD;
}
break;
case TwoPhaseOutcome.HEURISTIC_HAZARD:
if (p == TwoPhaseOutcome.HEURISTIC_MIXED)
heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
break;
case TwoPhaseOutcome.HEURISTIC_MIXED:
break;
default:
heuristicDecision = p; // anything!
break;
}
if (TxStats.enabled())
TxStats.getInstance().incrementHeuristics();
}
protected void updateState ()
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::updateState() for action-id "
+ get_uid());
}
/*
* If the action is topLevel then prepare() will have written the
* intention_list to the object_store. If any of the phase2Commit
* processing failed then records will exist on the failedList. If this
* is the case then we need to re-write the BasicAction record in the
* object store. If the failed list is empty we can simply delete the
* BasicAction record.
*/
if (actionType == ActionType.TOP_LEVEL)
{
/*
* make sure the object store is set up for a top-level atomic
* action.
*/
store();
/*
* If we have failures then rewrite the intentions list. Otherwise,
* delete the log entry.
*/
if (((failedList != null) && (failedList.size() > 0))
|| ((heuristicList != null) && (heuristicList.size() > 0)))
{
/*
* Re-write the BasicAction record with the failed list
*/
Uid u = getSavingUid();
String tn = type();
OutputObjectState state = new OutputObjectState(u, tn);
if (!save_state(state, ObjectType.ANDPERSISTENT))
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_64");
// what else?
}
if (state.notempty())
{
try
{
if (!currentStore.write_committed(u, tn, state))
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_65");
}
}
catch (ObjectStoreException e)
{
tsLogger.arjLogger.warn(e);
}
}
}
else
{
try
{
if (savedIntentionList)
{
if (currentStore.remove_committed(getSavingUid(), type()))
{
savedIntentionList = false;
}
}
}
catch (ObjectStoreException e)
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_70", new Object[]
{ e });
}
}
}
}
/*
* This is only meant as an instance cut of the children, so don't lock the
* entire transaction. Thus, the list may change before we return.
*/
private final void createPreparedLists ()
{
if (preparedList == null)
preparedList = new RecordList();
if (readonlyList == null)
readonlyList = new RecordList();
if (failedList == null)
failedList = new RecordList();
if (heuristicList == null)
heuristicList = new RecordList();
if (pendingList == null)
pendingList = new RecordList();
}
/**
* Check to see if this transaction is the one that is current for this
* thread. If it isn't, then we mark this transaction as rollback only.
*
* @return <code>true</code> if the transaction is current,
* <code>false</code> otherwise.
*/
private final boolean checkIsCurrent ()
{
boolean isCurrent = true;
if (checkForCurrent())
{
BasicAction currentAct = BasicAction.Current();
/* Ensure I am the currently active action */
if ((currentAct != null) && (currentAct != this))
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_56", new Object[]
{ currentAct.get_uid(), get_uid() });
}
isCurrent = false;
if (currentAct.isAncestor(get_uid()))
{
/* current action is one of my children */
BasicAction parentAct = parent();
/* prevent commit of my parents (ensures safety) */
while (parentAct != null)
{
parentAct.preventCommit();
parentAct = parentAct.parent();
}
}
}
currentAct = null;
}
return isCurrent;
}
private final boolean checkChildren (boolean isCommit)
{
boolean problem = false;
/*
* If we have child threads then by default we just print a warning and
* continue. The other threads will eventually find out the outcome.
*/
if ((_childThreads != null) && (_childThreads.size() > 0))
{
if ((_childThreads.size() != 1)
|| ((_childThreads.size() == 1) && (!_childThreads.contains(Thread.currentThread()))))
{
/*
* More than one thread or the one thread is not the current
* thread
*/
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
if (isCommit)
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_57", new Object[]
{ get_uid() });
}
else
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_58", new Object[]
{ get_uid() });
}
}
if (_checkedAction != null)
_checkedAction.check(isCommit, get_uid(), _childThreads);
removeAllChildThreads();
}
}
/* Ensure I have no child actions */
if ((_childActions != null) && (_childActions.size() > 0))
{
problem = true;
Enumeration iter = _childActions.elements();
BasicAction child = null;
boolean printError = true;
/*
* We may have already aborted our children, e.g., because of an
* out-of-sequence commit, so we check here to reduce the number of
* error messages!
*
* We can't just remove the children when we are finished with them
* because BasicAction is not responsible for action tracking.
*/
while (iter.hasMoreElements())
{
child = (BasicAction) iter.nextElement();
if (child.status() != ActionStatus.ABORTED)
{
if (printError)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
if (isCommit)
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_59", new Object[]
{ get_uid() });
}
else
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_60", new Object[]
{ get_uid() });
}
}
printError = false;
}
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_61", new Object[]
{ child.get_uid() });
}
child.Abort();
child = null;
}
}
iter = null;
if (isCommit)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_62", new Object[]
{ ((child != null ? child.get_uid() : "null"))});
}
}
}
return problem;
}
/*
* Just in case we are deleted/terminated with threads still registered. We
* must make sure those threads don't try to remove themselves from this
* action later. So we unregister them ourselves now.
*
* This is only called by End/Abort and so all child actions will have been
* previously terminated as well.
*/
private final void removeAllChildThreads ()
{
/*
* Do not remove the current thread as it is committing/aborting!
*/
criticalStart();
if ((_childThreads != null) && (_childThreads.size() != 0))
{
Thread currentThread = Thread.currentThread();
/*
* Iterate through all registered threads and tell them to ignore
* the action pointer, i.e., they are now no longer within this
* action.
*/
Enumeration iter = _childThreads.elements();
Thread t = null;
while (iter.hasMoreElements())
{
t = (Thread) iter.nextElement();
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_63", new Object[]
{ get_uid(), t });
}
if (t != currentThread)
ThreadActionData.purgeAction(this, t);
}
}
criticalEnd();
}
/**
* actionInitialise determines whether the BasicAction is a nested,
* top-level, or a top-level nested atomic action
*/
private final void actionInitialise (BasicAction parent)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::actionInitialise() for action-id "
+ get_uid());
}
criticalStart();
if (parent != null) /* ie not top_level */
{
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_66", new Object[]
{ get_uid(), Integer.toString(parent.actionStatus) });
}
currentHierarchy = new ActionHierarchy(parent.getHierarchy());
}
else
{
currentHierarchy = new ActionHierarchy(
ActionHierarchy.DEFAULT_HIERARCHY_DEPTH);
/*
* This is a top-level atomic action so set the signal handler block
* a number of signals.
*/
}
currentHierarchy.add(get_uid(), actionType);
switch (actionType)
{
case ActionType.TOP_LEVEL:
if (parent != null)
{
/*
* do not want to print warning all the time as this is what
* nested top-level actions are used for.
*/
if (tsLogger.arjLoggerI18N.isDebugEnabled())
{
tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_67", new Object[]
{ get_uid(), parent.get_uid() });
}
}
break;
case ActionType.NESTED:
if (parent == null)
actionType = ActionType.TOP_LEVEL;
break;
}
parentAction = parent;
criticalEnd();
}
private final void doForget (RecordList list_toprocess)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::doForget ("
+ list_toprocess + ")");
}
/*
* If the user has selected to maintain all heuristic information, then
* we never explicitly tell resources to forget. We assume that the user
* (or some management tool) will do this, and simply save as much
* information as we can into the action state to allow them to do so.
*
* However, if we had a resource that returned a heuristic outcome and
* we managed to make the outcome of this transaction the same as that
* outcome, we removed the heuristic. So, we need to tell the resource
* regardless, or it'll never be able to tidy up.
*/
boolean force = (boolean) (heuristicDecision == TwoPhaseOutcome.FINISH_OK);
if (!TxControl.maintainHeuristics || force)
{
if (list_toprocess.size() > 0)
{
RecordList tmpList = new RecordList();
while (((recordBeingHandled = list_toprocess.getFront())) != null)
{
/*
* Remember for later if we cannot tell it to forget.
*/
if (recordBeingHandled.forgetHeuristic())
recordBeingHandled = null;
else
tmpList.putFront(recordBeingHandled);
}
/*
* Now put those resources we couldn't tell to forget back on
* the heuristic list.
*/
if (tmpList.size() > 0)
{
while ((recordBeingHandled = tmpList.getFront()) != null)
list_toprocess.putFront(recordBeingHandled);
}
}
}
}
/*
* Walk down a record list extracting records and calling the appropriate
* cleanup function. Discard records when done. NOTE: We only need to do
* cleanup at top level since cleanup at nested level would be subsumed when
* the parent action is forced to abort
*
* Ignore heuristics. Who can we report them to?
*
* This routine is called by phase2Cleanup, which gets called only in
* exceptional circumstances. By default we leave cleaning up the various
* lists until the action instance goes out of scope.
*/
private final void doCleanup (RecordList list_toprocess)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::doCleanup ("
+ list_toprocess + ")");
}
if (list_toprocess.size() > 0)
{
int ok = TwoPhaseOutcome.FINISH_OK;
while (((recordBeingHandled = list_toprocess.getFront()) != null))
{
if (actionType == ActionType.TOP_LEVEL)
ok = recordBeingHandled.topLevelCleanup();
else
ok = recordBeingHandled.nestedCleanup();
if ((actionType != ActionType.TOP_LEVEL)
&& (recordBeingHandled.propagateOnAbort()))
{
merge(recordBeingHandled);
}
else
{
if (ok != TwoPhaseOutcome.FINISH_OK)
{
/* Preserve error messages */
}
recordBeingHandled = null;
}
}
}
}
private final synchronized boolean doOnePhase ()
{
if (TxControl.onePhase)
{
return (((pendingList == null) || (pendingList.size() == 1)) ? true
: false);
}
else
return false;
}
/*
* Operation to merge a record into those held by the parent BasicAction.
* This is accomplished by invoking the add operation of the parent
* BasicAction. If the add operation does not return AR_ADDED, the record is
* deleted
*/
private final synchronized void merge (AbstractRecord A)
{
int as;
if ((as = parentAction.add(A)) != AddOutcome.AR_ADDED)
{
A = null;
if (as == AddOutcome.AR_REJECTED)
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_68");
}
}
/* These (genuine) lists hold the abstract records */
protected RecordList pendingList;
protected RecordList preparedList;
protected RecordList readonlyList;
protected RecordList failedList;
protected RecordList heuristicList;
protected boolean savedIntentionList;
private ActionHierarchy currentHierarchy;
private ObjectStore currentStore;
// private boolean savedIntentionList;
/* Atomic action status variables */
private int actionStatus;
private int actionType;
private BasicAction parentAction;
private AbstractRecord recordBeingHandled;
private int heuristicDecision;
private CheckedAction _checkedAction; // control what happens if threads active when terminating.
/*
* We need to keep track of the number of threads associated with each
* action. Since we can't override the basic thread methods, we have to
* provide an explicit means of registering threads with an action.
*/
private Hashtable _childThreads;
private Hashtable _childActions;
// private Mutex _lock = new Mutex(); // TODO
private static CheckedActionFactory _checkedActionFactory;
static
{
/*
* Make sure this can only be set once. Bad things can happen if the factory changes
* during execution.
*/
String checkedActionFactory = arjPropertyManager.getCoordinatorEnvironmentBean().getCheckedActionFactory();
if (checkedActionFactory != null)
{
try
{
Class factory = Thread.currentThread().getContextClassLoader().loadClass(checkedActionFactory);
_checkedActionFactory = (CheckedActionFactory) factory.newInstance();
}
catch (final Exception ex)
{
ex.printStackTrace();
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.checkedactionfactory",
new Object[]{ checkedActionFactory }, ex);
}
throw new FatalError(tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.coordinator.cafactoryerror")+" "+checkedActionFactory, ex);
}
}
else
_checkedActionFactory = new CheckedActionFactoryImple();
}
}