com.arjuna.ats.arjuna.coordinator.BasicAction Maven / Gradle / Ivy
/*
* 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import com.arjuna.ats.arjuna.ObjectType;
import com.arjuna.ats.arjuna.StateManager;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.objectstore.ParticipantStore;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;
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.thread.ThreadActionData;
/**
* 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 ([email protected])
* @version $Id: BasicAction.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*/
public class BasicAction extends StateManager
{
public BasicAction ()
{
super(ObjectType.NEITHER);
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::BasicAction()");
}
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
transactionStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = ActionType.NESTED;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = arjPropertyManager
.getCoordinatorEnvironmentBean().getCheckedActionFactory()
.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.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::BasicAction("
+ objUid + ")");
}
pendingList = null;
preparedList = null;
readonlyList = null;
failedList = null;
heuristicList = null;
currentHierarchy = null;
transactionStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = ActionType.NESTED;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = arjPropertyManager
.getCoordinatorEnvironmentBean().getCheckedActionFactory()
.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 finalizeInternal()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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())) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_1(get_uid());
while ((currentAct != this) && (currentAct != null)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_2(currentAct.get_uid());
currentAct.Abort();
currentAct = BasicAction.Current();
}
}
}
BasicAction parentAct = parent();
/* prevent commit of parents (safety) */
while (parentAct != null) {
parentAct.preventCommit();
parentAct = parentAct.parent();
}
tsLogger.i18NLogger.warn_coordinator_BasicAction_3(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;
transactionStore = 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 true
if successful, false
* otherwise.
*/
public final boolean preventCommit ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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 AddOutcome
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 true
if the parameter represents an ancestor,
* false
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 participantStore variable.
*
* @return the object store implementation to use.
* @see com.arjuna.ats.arjuna.objectstore.ObjectStore
*/
public ParticipantStore getStore ()
{
if (transactionStore == null)
{
transactionStore = StoreManager.getParticipantStore();
}
return transactionStore;
}
/**
* 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 Uid
.
*/
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 null
.
*
* @return true
if successful, false
* 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 root
parameter.
*
* @return true
if successful, false
* otherwise.
*/
public boolean activate (String root)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::activate() for action-id "
+ get_uid());
}
boolean restored = false;
// Set up store
ParticipantStore aaStore = getStore();
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 {
tsLogger.i18NLogger.warn_coordinator_BasicAction_5(get_uid(), type());
restored = false;
}
return restored;
}
catch (ObjectStoreException e)
{
tsLogger.logger.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 null
. It is assumed that
* this is being called during a transaction commit.
*
* @return true
on success, false
otherwise.
*
*/
public boolean deactivate ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::deactivate() for action-id "
+ get_uid());
}
boolean deactivated = false;
// Set up store
ParticipantStore aaStore = getStore();
if (aaStore == null)
return false;
try
{
// Write object state
OutputObjectState oState = new OutputObjectState();
if (save_state(oState, ObjectType.ANDPERSISTENT))
{
deactivated = aaStore.write_committed(getSavingUid(), type(), oState);
oState = null;
}
else
{
deactivated = false;
}
/** If we failed to deactivate then output warning * */
if (!deactivated) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_5a(get_uid(), type());
}
}
catch (ObjectStoreException e)
{
tsLogger.logger.warn(e);
deactivated = false;
}
return deactivated;
}
/**
* Add the current thread to the list of threads associated with this
* transaction.
*
* @return true
if successful, false
* 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 true
if successful, false
* otherwise.
*/
public final boolean addChildThread (Thread t)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::addChildThread () action "+get_uid()+" adding "+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.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::addChildThread () action "+get_uid()+" adding "+t+" result = "+result);
}
return result;
}
/*
* Can be done at any time (Is this correct?)
*/
/**
* Remove a child thread. The current thread is removed.
*
* @return true
if successful, false
* otherwise.
*/
public final boolean removeChildThread () // current thread
{
return removeChildThread(ThreadUtil.getThreadId());
}
/**
* Remove the specified thread from the transaction.
*
* @return true
if successful, false
* otherwise.
*/
public final boolean removeChildThread (String threadId)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::removeChildThread () action "+get_uid()+" removing "+threadId);
}
if (threadId == null)
return false;
boolean result = false;
criticalStart();
synchronized (this)
{
if (_childThreads != null)
{
_childThreads.remove(threadId);
result = true;
}
}
criticalEnd();
if (tsLogger.logger.isTraceEnabled())
{
tsLogger.logger.trace("BasicAction::removeChildThread () action "+get_uid()+" removing "+threadId+" result = "+result);
}
return result;
}
/**
* Add a new child action to the atomic action.
*
* @return true
if successful, false
* otherwise.
*/
public final boolean addChildAction (BasicAction act)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::addAction () action "+get_uid()+" adding "+((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.logger.isTraceEnabled())
{
tsLogger.logger.trace("BasicAction::addChildAction () action "+get_uid()+" adding "+act.get_uid()+" result = "+result);
}
return result;
}
/*
* Can be done at any time (Is this correct?)
*/
/**
* 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 true
if successful, false
* otherwise.
*/
public boolean save_state (OutputObjectState os, int ot)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::save_state ()");
}
try
{
packHeader(os, new Header(get_uid(), Utility.getProcessUid()));
os.packBoolean(pastFirstParticipant);
}
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.logger.isTraceEnabled())
{
tsLogger.logger.trace("BasicAction::save_state - next record to pack is a "+temp.typeIs()
+" record "+temp.type()+" should save it? = "+temp.doSave());
}
if (temp.doSave())
{
res = true;
try
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Packing a "+temp.typeIs()+" record");
}
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("Packing a NONE_RECORD");
}
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("HeuristicList - packing a "+temp.typeIs()+" record");
}
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("HeuristicList - packing a NONE_RECORD");
}
os.packInt(RecordType.NONE_RECORD);
}
catch (IOException e)
{
res = false;
}
}
}
}
if (res && os.notempty())
{
try
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Packing action status of "+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;
}
/**
* Remove a child action.
*
* @return true
if successful, false
* otherwise.
*/
public final boolean removeChildAction (BasicAction act)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::removeChildAction () action "+get_uid()+" removing "+((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.logger.isTraceEnabled())
{
tsLogger.logger.trace("BasicAction::removeChildAction () action "+get_uid()+" removing "+act.get_uid()+" result = "+result);
}
return result;
}
/**
* Add the specified CheckedAction object to this transaction.
*
* @see com.arjuna.ats.arjuna.coordinator.CheckedAction
*/
protected final synchronized void setCheckedAction (CheckedAction c)
{
criticalStart();
_checkedAction = c;
criticalEnd();
}
/**
* @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));
}
/**
* This assumes the various lists are zero length when it is called.
*
* @return true
if successful, false
* otherwise.
*/
public boolean restore_state (InputObjectState os, int ot)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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);
pastFirstParticipant = os.unpackBoolean();
}
catch (IOException e)
{
return false;
}
try
{
record_type = os.unpackInt();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Unpacked a "+record_type+" record");
}
}
catch (IOException e)
{
res = false;
}
while ((res) && (record_type != RecordType.NONE_RECORD))
{
AbstractRecord record = AbstractRecord.create(record_type);
if (record == null) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_21(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.logger.isTraceEnabled()) {
tsLogger.logger.trace("Unpacked a "+record_type+" record");
}
}
catch (IOException e)
{
res = false;
}
}
}
// Now deal with the heuristic list!
int hSize = 0;
if (res)
{
try
{
hSize = os.unpackInt();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("HeuristicList - Unpacked heuristic list size of "+hSize);
}
}
catch (IOException e)
{
res = false;
}
}
if (hSize > 0)
{
try
{
record_type = os.unpackInt();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("HeuristicList - Unpacked a "+record_type+" record");
}
}
catch (IOException e)
{
res = false;
}
while ((res) && (record_type != RecordType.NONE_RECORD))
{
AbstractRecord record = AbstractRecord.create(record_type);
try
{
res = (record.restore_state(os, ot) && heuristicList.insert(record));
record_type = os.unpackInt();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("HeuristicList - Unpacked a "+record_type+" record");
}
}
catch (IOException e)
{
res = false;
}
catch (final NullPointerException ex) {
tsLogger.i18NLogger.warn_coordinator_norecordfound(Integer.toString(record_type));
res = false;
}
}
}
if (res)
{
try
{
tempActionStatus = os.unpackInt();
tempActionType = os.unpackInt();
tempHeuristicDecision = os.unpackInt();
}
catch (IOException e) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_24();
res = false;
}
}
if (res)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Restored action status of "+ActionStatus.stringForm(tempActionStatus)+
" "+Integer.toString(tempActionStatus));
tsLogger.logger.trace("Restored action type "+
((tempActionType == ActionType.NESTED) ? "Nested" : "Top-level")+
" "+Integer.toString(tempActionType));
tsLogger.logger.trace(" Restored heuristic decision of "+
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 true
if the transaction should save its heuristic
* information, false
otherwise.
*/
public static boolean maintainHeuristics ()
{
return TxControl.maintainHeuristics;
}
/**
* Overloads StateManager.destroy
to prevent destroy being
* called on a BasicAction. Could be a *very* bad idea!!
*
* @return false
.
* @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 == null) ? 0 : _childActions.size());
if (size > 0)
{
Collection c = _childActions.values();
return c.toArray();
}
return null;
}
/**
* Get any Throwable that was caught during commit processing but not directly rethrown.
* @return a list of ThrowableS, if any
*/
public List getDeferredThrowables()
{
return deferredThrowables;
}
@Override
public boolean equals (java.lang.Object obj)
{
if (obj instanceof BasicAction)
{
if (((BasicAction) obj).get_uid().equals(get_uid()))
return true;
}
return false;
}
@Override
public int hashCode()
{
return get_uid().hashCode();
}
/**
* Forget any heuristics we may have received, and tell the resources which
* generated them to forget too.
*
* @return true
if heuristic information (if any) was
* successfully forgotten, false
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 ActionStatus
indicating outcome.
*/
protected synchronized int Begin (BasicAction parentAct)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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;
tsLogger.i18NLogger.warn_coordinator_notrunning();
}
else
{
if (actionStatus != ActionStatus.CREATED) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_29(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) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_30(get_uid());
}
else
{
tsLogger.i18NLogger.warn_coordinator_BasicAction_31(get_uid(), parentAct.get_uid(), Integer.toString(parentAct.status()));
}
}
ActionManager.manager().put(this);
if(finalizeBasicActions) {
finalizerObject = new BasicActionFinalizer(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
* true
.
*
* @return ActionStatus
indicating outcome.
*/
protected synchronized int End (boolean reportHeuristics)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::End() for action-id "
+ get_uid());
}
/* Check for superfluous invocation */
if ((actionStatus != ActionStatus.RUNNING)
&& (actionStatus != ActionStatus.ABORT_ONLY)) {
switch (actionStatus) {
case ActionStatus.CREATED:
tsLogger.i18NLogger.warn_coordinator_BasicAction_33(get_uid());
break;
case ActionStatus.COMMITTED:
tsLogger.i18NLogger.warn_coordinator_BasicAction_34(get_uid());
break;
default:
tsLogger.i18NLogger.warn_coordinator_BasicAction_35(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) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_36(get_uid());
if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_37(TwoPhaseOutcome.stringForm(heuristicDecision));
}
tsLogger.i18NLogger.warn_coordinator_BasicAction_38();
if (!reportHeuristics && TxControl.asyncCommit
&& (parentAction == null)) {
TwoPhaseCommitThreadPool.submitJob(new AsyncCommit(this, false));
} else
phase2Abort(reportHeuristics); /* first phase failed */
}
else
{
if (!reportHeuristics && TxControl.asyncCommit
&& (parentAction == null))
{
TwoPhaseCommitThreadPool.submitJob(new AsyncCommit(this, true));
}
else
phase2Commit(reportHeuristics); /* first phase succeeded */
}
}
}
else
{
ActionManager.manager().remove(get_uid());
actionStatus = ActionStatus.COMMITTED;
if (TxStats.enabled())
{
if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_ROLLBACK)
{
TxStats.getInstance().incrementCommittedTransactions();
}
}
}
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.tracef("BasicAction::End() result for action-id (%s) is (%s) node id: (%s)",
get_uid(),
TwoPhaseOutcome.stringForm(heuristicDecision),
arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
}
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 ActionStatus
indicating outcome.
*/
protected synchronized int Abort ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::Abort() for action-id "
+ get_uid());
}
/* Check for superfluous invocation */
if ((actionStatus != ActionStatus.RUNNING)
&& (actionStatus != ActionStatus.ABORT_ONLY)
&& (actionStatus != ActionStatus.COMMITTING)) {
switch (actionStatus) {
case ActionStatus.CREATED:
tsLogger.i18NLogger.warn_coordinator_BasicAction_39(get_uid());
break;
case ActionStatus.ABORTED:
tsLogger.i18NLogger.warn_coordinator_BasicAction_40(get_uid());
break;
default:
tsLogger.i18NLogger.warn_coordinator_BasicAction_41(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;
transactionStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = at;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = arjPropertyManager
.getCoordinatorEnvironmentBean().getCheckedActionFactory()
.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;
transactionStore = null;
savedIntentionList = false;
actionStatus = ActionStatus.CREATED;
actionType = at;
parentAction = null;
recordBeingHandled = null;
heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
_checkedAction = arjPropertyManager
.getCoordinatorEnvironmentBean().getCheckedActionFactory()
.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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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().
*
* @throws Error JBTM-895 tests, byteman limitation
*/
protected synchronized final void phase2Commit (boolean reportHeuristics) throws Error
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::phase2Commit() for action-id "
+ get_uid());
}
if ((pendingList != null) && (pendingList.size() > 0)) {
int size = ((pendingList == null) ? 0 : pendingList.size());
tsLogger.i18NLogger.warn_coordinator_BasicAction_42(get_uid(), Integer.toString(size), pendingList.toString());
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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();
}
}
protected int async_prepare(boolean reportHeuristics) {
int p = TwoPhaseOutcome.PREPARE_OK;
AbstractRecord lastRec = pendingList.getRear();
Collection> tasks = new ArrayList>();
while (pendingList.size() != 0)
tasks.add(TwoPhaseCommitThreadPool.submitJob(new AsyncPrepare(
this, reportHeuristics, pendingList.getFront())));
// prepare the last (or only) participant on the callers thread
if (lastRec != null)
p = doPrepare(reportHeuristics, lastRec);
// get the results
for (Future task : tasks) {
try {
int outcome = task.get();
if (p == TwoPhaseOutcome.PREPARE_OK)
p = outcome;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return p;
}
/**
* 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 TwoPhaseOutcome
indicating outcome.
*/
protected synchronized final int prepare (boolean reportHeuristics)
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_43(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 (getStore() == 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))
{
p = async_prepare(reportHeuristics);
}
else
{
// single threaded prepare
// createPreparedLists will have ensured list exists, but it may be empty
if(pendingList.size() > 0) {
p = doPrepare(reportHeuristics);
}
}
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 {
tsLogger.i18NLogger.warn_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)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_45(get_uid());
criticalEnd();
return TwoPhaseOutcome.PREPARE_NOTOK;
}
if (state.notempty())
{
try
{
if (!transactionStore.write_committed(u, tn, state)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_46(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.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::onePhaseCommit() for action-id "
+ get_uid());
}
/* Are we forced to abort? */
if (actionStatus == ActionStatus.ABORT_ONLY) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_43(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.ONE_PHASE_ERROR) {
addDeferredThrowables(recordBeingHandled, deferredThrowables);
}
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
{
addDeferredThrowables(recordBeingHandled, deferredThrowables);
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!!
*/
tsLogger.i18NLogger.warn_coordinator_BasicAction_47(get_uid(), TwoPhaseOutcome.stringForm(p));
if (reportHeuristics) {
updateHeuristic(p, true);
if (!heuristicList.insert(recordBeingHandled))
recordBeingHandled = null;
else {
addDeferredThrowables(recordBeingHandled, deferredThrowables);
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 (getStore() == null)
{
tsLogger.i18NLogger.fatal_coordinator_BasicAction_48();
throw new com.arjuna.ats.arjuna.exceptions.FatalError(
tsLogger.i18NLogger.get_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;
}
/**
* WARNING: use with extreme care!
*/
protected final synchronized void setHeuristicDecision (int p)
{
heuristicDecision = p;
}
/**
* 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;
}
/*
* 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!
*/
private int doPrepare (boolean reportHeuristics)
{
/*
* 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 overallTwoPhaseOutcome = TwoPhaseOutcome.PREPARE_READONLY;
/*
* 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.
*/
boolean keepGoing = true;
while(pendingList.size() > 0 && keepGoing) {
AbstractRecord record = pendingList.getFront();
/*
* 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.
*/
int individualTwoPhaseOutcome = doPrepare(reportHeuristics, record);
if(individualTwoPhaseOutcome != TwoPhaseOutcome.PREPARE_READONLY) {
overallTwoPhaseOutcome = individualTwoPhaseOutcome;
}
keepGoing = ( individualTwoPhaseOutcome == TwoPhaseOutcome.PREPARE_OK) || ( individualTwoPhaseOutcome == TwoPhaseOutcome.PREPARE_READONLY);
}
return overallTwoPhaseOutcome;
}
/*
* 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 record)
{
/*
* 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;
p = ((actionType == ActionType.TOP_LEVEL) ? record.topLevelPrepare()
: record.nestedPrepare());
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.tracef(
"BasicAction::doPrepare() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
get_uid(), record.order(), TwoPhaseOutcome.stringForm(p),
arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
}
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)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_49(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.
*/
addDeferredThrowables(record, deferredThrowables);
record = insertRecord(pendingList, record);
record = null;
actionStatus = ActionStatus.PREPARED;
return p;
}
else {
/*
* Heuristic decision!!
*/
/*
* Only report if request to do so.
*/
tsLogger.i18NLogger.warn_coordinator_BasicAction_50(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.)
*/
}
}
}
}
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;
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction.doCommit for "+get_uid()+" received "+
TwoPhaseOutcome.stringForm(ok)+" from "+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);
addDeferredThrowables(recordBeingHandled, deferredThrowables);
}
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.
*/
if ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
|| (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
|| (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD))
{
updateHeuristic(ok, true);
}
failedList.insert(recordBeingHandled);
addDeferredThrowables(recordBeingHandled, deferredThrowables);
}
}
}
}
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 */
}
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.tracef(
"BasicAction::doCommit() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
get_uid(), record.order(), TwoPhaseOutcome.stringForm(ok),
arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
}
}
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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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 (actionType == ActionType.TOP_LEVEL)
tsLogger.i18NLogger.warn_coordinator_BasicAction_52(get_uid(), TwoPhaseOutcome.stringForm(ok));
else
tsLogger.i18NLogger.warn_coordinator_BasicAction_53(get_uid(), TwoPhaseOutcome.stringForm(ok));
updateHeuristic(ok, false);
heuristicList.insert(recordBeingHandled);
addDeferredThrowables(recordBeingHandled, deferredThrowables);
}
else
{
if (ok != TwoPhaseOutcome.FINISH_OK) {
if (actionType == ActionType.TOP_LEVEL)
tsLogger.i18NLogger.warn_coordinator_BasicAction_54(get_uid(),
TwoPhaseOutcome.stringForm(ok),
RecordType.typeToClass(recordBeingHandled.typeIs()).getCanonicalName());
else
tsLogger.i18NLogger.warn_coordinator_BasicAction_55(get_uid(),
TwoPhaseOutcome.stringForm(ok),
RecordType.typeToClass(recordBeingHandled.typeIs()).getCanonicalName());
}
}
}
/*
* Don't need a canDelete as in the C++ version since Java's
* garbage collection will deal with things for us.
*/
recordBeingHandled = null;
}
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.tracef(
"BasicAction::doAbort() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
get_uid(), record.order(), TwoPhaseOutcome.stringForm(ok),
arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
}
}
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 false
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)
{
// only count the first heuristic.
if (TxStats.enabled())
TxStats.getInstance().incrementHeuristics();
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;
}
}
protected void updateState ()
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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.
*/
getStore();
/*
* If we have failures then rewrite the intentions list. Otherwise,
* delete the log entry. Depending upon how we get here the intentions
* list will either be in the preparedList or the failedList. Fortunately
* save_state will figure out which one to use.
*/
if (((failedList != null) && (failedList.size() > 0))
|| ((heuristicList != null) && (heuristicList.size() > 0))
|| ((preparedList != null) && (preparedList.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.i18NLogger.warn_coordinator_BasicAction_64();
// what else?
}
if (state.notempty())
{
try
{
if (!transactionStore.write_committed(u, tn, state)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_65();
}
}
catch (ObjectStoreException e)
{
tsLogger.logger.warn(e);
}
}
}
else
{
try
{
if (savedIntentionList)
{
if (transactionStore.remove_committed(getSavingUid(), type()))
{
savedIntentionList = false;
}
}
}
catch (ObjectStoreException e) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_70(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 true
if the transaction is current,
* false
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)) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_56(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 (isCommit) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_57(get_uid());
} else {
tsLogger.i18NLogger.warn_coordinator_BasicAction_58(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 = iter.nextElement();
if (child.status() != ActionStatus.ABORTED) {
if (printError) {
if (isCommit) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_59(get_uid());
} else {
tsLogger.i18NLogger.warn_coordinator_BasicAction_60(get_uid());
}
printError = false;
}
tsLogger.i18NLogger.warn_coordinator_BasicAction_61(child.get_uid());
child.Abort();
child = null;
}
}
iter = null;
if (isCommit) {
tsLogger.i18NLogger.warn_coordinator_BasicAction_62(((child != null ? child.get_uid().toString() : "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 = iter.nextElement();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::removeAllChildThreads () action "+get_uid()+" removing "+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.logger.isTraceEnabled()) {
tsLogger.logger.trace("BasicAction::actionInitialise() for action-id "
+ get_uid());
}
criticalStart();
if (parent != null) /* ie not top_level */
{
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Action "+get_uid()+" with parent status "+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.logger.isTraceEnabled()) {
tsLogger.logger.trace("Running Top Level Action "+get_uid()+" from within " +
"nested action ("+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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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.logger.isTraceEnabled()) {
tsLogger.logger.trace("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.i18NLogger.warn_coordinator_BasicAction_68();
}
}
/* Adds the deferred throwables of the given record to the given list of throwables. */
private void addDeferredThrowables(AbstractRecord record, List throwables)
{
if (record instanceof ExceptionDeferrer)
((ExceptionDeferrer) record).getDeferredThrowables(throwables);
else if (record.value() instanceof ExceptionDeferrer)
((ExceptionDeferrer) record.value()).getDeferredThrowables(throwables);
}
/* 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 ParticipantStore transactionStore; // a ParticipantStore is also a TxLog
// 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.
private boolean pastFirstParticipant; // remember where we are (were) in committing during recovery
/*
* 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 BasicActionFinalizer finalizerObject;
private static final boolean finalizeBasicActions = arjPropertyManager.getCoordinatorEnvironmentBean().isFinalizeBasicActions();
// private Mutex _lock = new Mutex(); // TODO
private List deferredThrowables = new ArrayList();
}
class BasicActionFinalizer
{
private final BasicAction basicAction;
BasicActionFinalizer(BasicAction basicAction)
{
this.basicAction = basicAction;
}
@Override
protected void finalize() throws Throwable
{
basicAction.finalizeInternal();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy