/* * 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) 2000, 2001, * * Arjuna Solutions Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: XARecoveryModule.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jta.recovery.arjunacore; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.Vector; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.exceptions.ObjectStoreException; import com.arjuna.ats.arjuna.logging.tsLogger; import com.arjuna.ats.arjuna.objectstore.RecoveryStore; import com.arjuna.ats.arjuna.objectstore.StateStatus; import com.arjuna.ats.arjuna.objectstore.StoreManager; import com.arjuna.ats.arjuna.recovery.RecoveryManager; import com.arjuna.ats.arjuna.recovery.RecoveryModule; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; import com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord; import com.arjuna.ats.jta.common.jtaPropertyManager; import com.arjuna.ats.jta.logging.jtaLogger; import com.arjuna.ats.jta.recovery.SerializableXAResourceDeserializer; import com.arjuna.ats.jta.recovery.XARecoveryResource; import com.arjuna.ats.jta.recovery.XARecoveryResourceManager; import com.arjuna.ats.jta.recovery.XAResourceOrphanFilter; import com.arjuna.ats.jta.recovery.XAResourceRecovery; import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; import com.arjuna.ats.jta.utils.XAHelper; import com.arjuna.ats.jta.utils.XARecoveryResourceHelper; import org.jboss.tm.XAResourceWrapper; /** * Designed to be able to recover any XAResource. */ public class XARecoveryModule implements RecoveryModule { public XARecoveryModule() { this(new com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryResourceManagerImple(), "Local XARecoveryModule"); com.arjuna.ats.internal.jta.Implementations.initialise(); } public Set<String> getContactedJndiNames() { return Collections.unmodifiableSet(contactedJndiNames); } public void addXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) { _xaResourceRecoveryHelpers.add(xaResourceRecoveryHelper); } public void removeXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) { synchronized (scanState) { // the first pass collects xa resources from recovery helpers - wait for it to finish // we can't just wait for BETWEEN_PHASES or even SECOND_PASS because its possible // that we would miss seeing those states when there is contention waitForNotScanState(ScanStates.FIRST_PASS); if (!getScanState().equals(ScanStates.IDLE)) { /* * check whether any resources found in the first pass were provided by * the target xaResourceRecoveryHelper and if so then we need to wait for second pass * of the scanner to finish */ if (isHelperInUse(xaResourceRecoveryHelper)) waitForScanState(ScanStates.IDLE); } _xaResourceRecoveryHelpers.remove(xaResourceRecoveryHelper); } } public void addXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) { synchronized (_xaResourceOrphanFilters) { if(!_xaResourceOrphanFilters.contains(xaResourceOrphanFilter)) { _xaResourceOrphanFilters.add(xaResourceOrphanFilter); } } } public void removeXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) { synchronized (_xaResourceOrphanFilters) { _xaResourceOrphanFilters.remove(xaResourceOrphanFilter); } } public void addSerializableXAResourceDeserializer(SerializableXAResourceDeserializer serializableXAResourceDeserializer) { _seriablizableXAResourceDeserializers.add(serializableXAResourceDeserializer); } public List<SerializableXAResourceDeserializer> getSeriablizableXAResourceDeserializers() { return _seriablizableXAResourceDeserializers; } public synchronized void periodicWorkFirstPass() { periodicWorkFirstPass(ScanStates.BETWEEN_PASSES); } private synchronized void periodicWorkFirstPass(ScanStates endState) { // JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least // the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering // the reason we can't do bottom up recovery is if this server has an XAResource which tries to recover a remote server (e.g. distributed JTA) // then we get deadlock on the secondpass if (getScanState() == ScanStates.BETWEEN_PASSES) { periodicWorkSecondPass(); } setScanState(ScanStates.FIRST_PASS); // synchronized uses a reentrant lock if(jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debugv("{0} - first pass", _logName); } contactedJndiNames.clear(); _uids = new InputObjectState(); /* * Scan for resources in the object store. */ try { if (!_recoveryStore.allObjUids(_recoveryManagerClass.type(), _uids)) { jtaLogger.i18NLogger.warn_recovery_alluids(); } } catch (ObjectStoreException e) { jtaLogger.i18NLogger.warn_recovery_objstoreerror(e); } catch (Exception e) { jtaLogger.i18NLogger.warn_recovery_periodicfirstpass(_logName+".periodicWorkFirstPass", e); } // JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least // the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering // scan using statically configured plugins; _resources = resourceInitiatedRecovery(); // scan using dynamically configured plugins: _resources.addAll(resourceInitiatedRecoveryForRecoveryHelpers()); List<XAResource> resources = new ArrayList<XAResource>(_resources); for (XAResource xaResource : resources) { try { xaRecoveryFirstPass(xaResource); } catch (Exception ex) { jtaLogger.i18NLogger.warn_recovery_getxaresource(ex); } } if (endState != ScanStates.BETWEEN_PASSES) { for (XAResource xaResource : resources) { try { xaResource.recover(XAResource.TMENDRSCAN); } catch (Exception ex) { jtaLogger.i18NLogger.warn_recovery_getxaresource(ex); } } } setScanState(endState); } public synchronized void periodicWorkSecondPass() { setScanState(ScanStates.SECOND_PASS); if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debugv("{0} - second pass", _logName); } try { // do the recovery on anything from the scan in first pass transactionInitiatedRecovery(); if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug(_logName + ".transactionInitiatedRecovery completed"); } bottomUpRecovery(); if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug(_logName + ".resourceInitiatedRecovery completed"); } } catch (Exception e) { jtaLogger.i18NLogger.warn_recovery_periodicsecondpass(_logName+".periodicWorkSecondPass", e); } clearAllFailures(); setScanState(ScanStates.IDLE); } public static XARecoveryModule getRegisteredXARecoveryModule () { if (registeredXARecoveryModule == null) { RecoveryManager recMan = RecoveryManager.manager(); Vector recoveryModules = recMan.getModules(); if (recoveryModules != null) { Enumeration modules = recoveryModules.elements(); while (modules.hasMoreElements()) { RecoveryModule m = (RecoveryModule) modules.nextElement(); if (m instanceof XARecoveryModule) { registeredXARecoveryModule = (XARecoveryModule) m; break; } } } } return registeredXARecoveryModule; } public String id() { return "XARecoveryModule:" + _recoveryManagerClass; } /** * @param xid The transaction to commit/rollback. * * @return the XAResource than can be used to commit/rollback the specified * transaction. */ private XAResource getNewXAResource(Xid xid) { XAResource toReturn = getTheKey(xid); if (toReturn == null) { synchronized (this) { /* * run an xid scan with the lock held to avoid _xidScans being changed * after the call to periodicWorkFirstPass but before the call to getTheKey */ periodicWorkFirstPass(ScanStates.IDLE); toReturn = getTheKey(xid); } } return toReturn; } private XAResource getTheKey(Xid xid) { if (_xidScans != null) { Enumeration<XAResource> keys = _xidScans.keys(); while (keys.hasMoreElements()) { XAResource theKey = keys.nextElement(); RecoveryXids xids = _xidScans.get(theKey); // JBTM-1255 moved stale check back to bottomUpRecovery if (xids.contains(xid)) { // This Xid will hopefully be recovered by the AtomicAction // We do not remove it from the map as there is garbage collection in RecoveryXids already // and it is possible that the Xid is recovered by both txbridge and XATerminator - the second // would get noxaresource error message return theKey; } } } return null; } /** * @param xaResourceRecord The record to reassociate. * * @return the XAResource than can be used to commit/rollback the specified * record. */ public XAResource getNewXAResource(XAResourceRecord xaResourceRecord) { return getNewXAResource(xaResourceRecord.getXid()); } protected XARecoveryModule(XARecoveryResourceManager recoveryClass, String logName) { _logName = logName; _recoveryManagerClass = recoveryClass; if(_recoveryManagerClass == null) { jtaLogger.i18NLogger.warn_recovery_constfail(); } _xaRecoverers = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceRecoveries(); _xaResourceOrphanFilters = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceOrphanFilters(); } private final boolean transactionInitiatedRecovery() { Uid theUid = null; while (Uid.nullUid().notEquals(theUid)) { try { theUid = UidHelper.unpackFrom(_uids); if (theUid.notEquals(Uid.nullUid())) { /* * Ignore it if it isn't in the store any more. Transaction * probably recovered it. */ if (_recoveryStore.currentState(theUid, _recoveryManagerClass .type()) != StateStatus.OS_UNKNOWN) { boolean problem = false; XARecoveryResource record = null; try { record = _recoveryManagerClass.getResource(theUid); problem = true; switch (record.recoverable()) { case XARecoveryResource.RECOVERY_REQUIRED: { if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("XARecovery attempting recovery of " + theUid); } int recoveryStatus = record.recover(); if (recoveryStatus != XARecoveryResource.RECOVERED_OK) { if (recoveryStatus == XARecoveryResource.WAITING_FOR_RECOVERY) { // resource initiated recovery not possible (no distribution). problem = false; jtaLogger.i18NLogger.info_recovery_recoverydelayed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus)); } else { jtaLogger.i18NLogger.warn_recovery_recoveryfailed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus)); } } else problem = false; } break; case XARecoveryResource.INFLIGHT_TRANSACTION: { /* * Transaction was inflight and between us * noticing it and trying to access the state, * it finished and removed the state. */ problem = false; } break; case XARecoveryResource.INCOMPLETE_STATE: default: { if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("XARecovery " + theUid + " is non-recoverable"); } } break; } } catch (NullPointerException ex) { problem = true; } catch (Throwable e) { problem = true; jtaLogger.i18NLogger.warn_recovery_recoveryerror(e); } if (problem && (record != null)) { /* * Some error occurred which prevented the state of * the resource from being read from the log. Hence * we don't have a valid key to use to insert it * into the list of records to be recovered. Print a * warning and move on. Force recovery via the * administration tool. Should be a rare occurrence. */ if (record.getXid() == null) { jtaLogger.i18NLogger.warn_recovery_cannotadd(); } else { addFailure(record.getXid(), record.get_uid()); } } } } } catch (IOException e) { theUid = Uid.nullUid(); } catch (Throwable e) { jtaLogger.i18NLogger.warn_recovery_unexpectedrecoveryerror(e); } } return true; } /** * * JBTM-895 garbage collection is now done when we return XAResources @see XARecoveryModule#getNewXAResource(XAResourceRecord) * @see XARecoveryModule#getNewXAResource(XAResourceRecord) */ private void bottomUpRecovery() { for (XAResource xaResource : _resources) { try { xaRecoverySecondPass(xaResource); } catch (Exception ex) { jtaLogger.i18NLogger.warn_recovery_getxaresource(ex); } } // JBTM-895 garbage collection is now done when we return XAResources {@see XARecoveryModule#getNewXAResource(XAResourceRecord)} // JBTM-924 requires this here garbage collection, see JBTM-1155: if (_xidScans != null) { Set<XAResource> keys = new HashSet<XAResource>(_xidScans.keySet()); for(XAResource theKey : keys) { RecoveryXids recoveryXids = _xidScans.get(theKey); if(recoveryXids.isStale()) { _xidScans.remove(theKey); } } } } /** * Now check for any outstanding transactions. If we didn't fail to recover * them, then roll them back - if they'd got through prepare we would have * an entry within the object store. * * Rely upon _xaRecoverers being set up properly (via properties). * * We cannot just remember the XAResourceRecords we used (if any) to cache * the JDBC connection information and use that since we may never have had * any such records! * * IMPORTANT: resourceInitiatedRecovery may rollback transactions which are * inflight: just because we have no entry for a transaction in the object * store does not mean it does not exist - it may be *about* to write its * intentions list. To try to reduce this probability we remember potential * rollback-ees at this scan, and wait for the next scan before actually * rolling them back. * * Note we cannot use the method that works with Transactions and * TransactionalObjects, of checking with original process that created the * transaction, because we don't know which process it was. */ private final List<XAResource> resourceInitiatedRecovery() { /* * Now any additional connections we may need to create. Relies upon * information provided by the application. */ List<XAResource> xaresources = new ArrayList<XAResource>(); if (_xaRecoverers.size() > 0) { for (int i = 0; i < _xaRecoverers.size(); i++) { try { XAResourceRecovery ri = (XAResourceRecovery) _xaRecoverers.get(i); while (ri.hasMoreResources()) { xaresources.add(ri.getXAResource()); } } catch (Exception ex) { jtaLogger.i18NLogger.warn_recovery_getxaresource(ex); } } } return xaresources; } private List<XAResource> resourceInitiatedRecoveryForRecoveryHelpers() { List<XAResource> xaresources = new ArrayList<XAResource>(); recoveryHelpersXAResource.clear(); for (XAResourceRecoveryHelper xaResourceRecoveryHelper : _xaResourceRecoveryHelpers) { try { XAResource[] xaResources = xaResourceRecoveryHelper.getXAResources(); if (xaResources != null) { for (XAResource xaResource : xaResources) { xaresources.add(xaResource); } recoveryHelpersXAResource.put(xaResourceRecoveryHelper, xaResources); } } catch (Exception ex) { jtaLogger.i18NLogger.warn_recovery_getxaresource(ex); } } return xaresources; } private final void xaRecoveryFirstPass(XAResource xares) { if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("xarecovery of " + xares); } Xid[] trans = null; try { trans = xares.recover(XAResource.TMSTARTRSCAN); if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("Found " + ((trans != null) ? trans.length : 0) + " xids in doubt"); } if (jtaLogger.logger.isTraceEnabled()) { for (Xid xid : trans) { byte[] globalTransactionId = xid.getGlobalTransactionId(); byte[] branchQualifier = xid.getBranchQualifier(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("< "); stringBuilder.append(xid.getFormatId()); stringBuilder.append(", "); stringBuilder.append(globalTransactionId.length); stringBuilder.append(", "); stringBuilder.append(branchQualifier.length); stringBuilder.append(", "); for (int i = 0; i < globalTransactionId.length; i++) { stringBuilder.append(globalTransactionId[i]); } stringBuilder.append(", "); for (int i = 0; i < branchQualifier.length; i++) { stringBuilder.append(branchQualifier[i]); } stringBuilder.append(" >"); jtaLogger.logger.debug("Recovered: " + stringBuilder.toString()); } } } catch (XAException e) { jtaLogger.i18NLogger.warn_recovery_xarecovery1(_logName+".xaRecovery", XAHelper.printXAErrorCode(e), e); try { xares.recover(XAResource.TMENDRSCAN); } catch (Exception e1) { } _xidScans.remove(xares); return; } RecoveryXids xidsToRecover = null; if (_xidScans == null) _xidScans = new Hashtable<XAResource,RecoveryXids>(); else { refreshXidScansForEquivalentXAResourceImpl(xares, trans); xidsToRecover = _xidScans.get(xares); if (xidsToRecover == null) { // this is probably redundant now due to updateIfEquivalentRM, // but in some implementations hashcode/equals does not behave itself. java.util.Enumeration<RecoveryXids> elements = _xidScans.elements(); boolean found = false; while (elements.hasMoreElements()) { xidsToRecover = elements.nextElement(); if (xidsToRecover.isSameRM(xares)) { found = true; break; } } if (!found) xidsToRecover = null; } } if (xidsToRecover == null) { xidsToRecover = new RecoveryXids(xares); _xidScans.put(xares, xidsToRecover); } xidsToRecover.nextScan(trans); saveContactedJndiName(xares); } private void xaRecoverySecondPass(XAResource xares) { if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("xarecovery second pass of " + xares); } RecoveryXids xidsToRecover = _xidScans.get(xares); if (xidsToRecover != null) { try { Xid[] xids = xidsToRecover.toRecover(); if (xids != null) { if (jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("Have " + xids.length + " Xids to recover on this pass."); } for (int j = 0; j < xids.length; j++) { boolean doForget = false; /* * Check if in failure list. */ Uid recordUid = null; boolean foundTransaction = false; do { // is the xid known to be one that couldn't be recovered recordUid = previousFailure(xids[j]); if ((recordUid == null) && (foundTransaction)) break; // end // of // recovery // for // this // transaction if (recordUid == null) { /* * It wasn't an xid that we couldn't recover, so the * RM knows about it, but we don't. Therefore it may * have to be rolled back. */ doForget = handleOrphan(xares, xids[j]); } else { foundTransaction = true; /* * In the failures list so it may be that we just * need another XAResource to be able to recover * this. */ XARecoveryResource record = _recoveryManagerClass .getResource(recordUid, xares); int recoveryStatus = record.recover(); if (recoveryStatus != XARecoveryResource.RECOVERED_OK) { jtaLogger.i18NLogger.warn_recovery_failedtorecover(_logName+".xaRecovery", XARecoveryResourceHelper.stringForm(recoveryStatus)); } removeFailure(record.getXid(), record.get_uid()); } if (doForget) { try { xares.forget(xids[j]); } catch (Exception e) { jtaLogger.i18NLogger.warn_recovery_forgetfailed(_logName+".xaRecovery", e); } } } while (recordUid != null); } } } catch (Exception e) { jtaLogger.i18NLogger.warn_recovery_generalrecoveryerror(_logName + ".xaRecovery", e); } try { if (xares != null) xares.recover(XAResource.TMENDRSCAN); } catch (XAException e) { jtaLogger.i18NLogger.warn_recovery_xarecovery1(_logName+".xaRecovery", XAHelper.printXAErrorCode(e), e); } } return; } /** * Apply use configurable filtering to determine how to handle the in-doubt resource. * * @param xares * @param xid * @return true if forget should be called, false otherwise. */ private boolean handleOrphan(XAResource xares, Xid xid) { // be default we play it safe and leave resources alone unless a filter explicitly recognizes them. // getting presumed abort behaviour therefore requires appropriate filters to be registered. XAResourceOrphanFilter.Vote votingOutcome = XAResourceOrphanFilter.Vote.LEAVE_ALONE; for(XAResourceOrphanFilter filter : _xaResourceOrphanFilters) { XAResourceOrphanFilter.Vote vote = filter.checkXid(xid); if(jtaLogger.logger.isDebugEnabled()) { jtaLogger.logger.debug("XAResourceOrphanFilter " + filter.getClass().getName() + " voted " + vote); } if(vote == XAResourceOrphanFilter.Vote.LEAVE_ALONE) { return false; } else if(vote == XAResourceOrphanFilter.Vote.ROLLBACK) { votingOutcome = vote; } } try { if(votingOutcome == XAResourceOrphanFilter.Vote.ROLLBACK) { jtaLogger.i18NLogger.info_recovery_rollingback(XAHelper.xidToString(xid)); xares.rollback(xid); } } catch (XAException e1) { jtaLogger.i18NLogger.warn_recovery_xarecovery1(_logName+".xaRecovery", XAHelper.printXAErrorCode(e1), e1); switch (e1.errorCode) { case XAException.XAER_RMERR: break; case XAException.XA_HEURHAZ: case XAException.XA_HEURCOM: case XAException.XA_HEURMIX: case XAException.XA_HEURRB: case XAException.XA_RBROLLBACK: { return true; } default: break; } } catch (Exception e2) { jtaLogger.i18NLogger.warn_recovery_xarecovery2(_logName+".xaRecovery", e2); } return false; } /** * For some drivers, isSameRM is connection specific. If we have prev scan results * for the same RM but using a different connection, we need to be able to identify them. * Look at the data from previous scans, identify any for the same RM but different XAResource * by checking for matching Xids, then replace the old XAResource with the supplied one. * * @param xares * @param xids */ private void refreshXidScansForEquivalentXAResourceImpl(XAResource xares, Xid[] xids) { Set<XAResource> keys = new HashSet<XAResource>(_xidScans.keySet()); for(XAResource theKey : keys) { RecoveryXids recoveryXids = _xidScans.get(theKey); if(recoveryXids.updateIfEquivalentRM(xares, xids)) { // recoveryXids is for this xares, but was originally obtained using // a different XAResource. rekey the hashtable to use the new one. _xidScans.remove(theKey); _xidScans.put(xares, recoveryXids); _resources.remove(theKey); // There could be two datasources pointed at the same resource manager if (!_resources.contains(xares)) { _resources.add(xares); } } } } /** * Is the Xid is in the failure list, i.e., the list of those transactions * we couldn't recover, possibly because of transient failures. If so, * return the uid of (one of) the records and remove it from the list. */ private final Uid previousFailure(Xid xid) { if (_failures == null) { return null; } Enumeration e = _failures.keys(); while (e.hasMoreElements()) { Xid theXid = (Xid) e.nextElement(); if (XAHelper.sameXID(xid, theXid)) { // remove uid from failure list Vector failureItem = (Vector) _failures.get(theXid); Uid u = (Uid) failureItem.remove(0); if (failureItem.size() == 0) _failures.remove(theXid); return u; } } // not present in the failures list. return null; } /* methods to manipulate the failure list */ /** * Add record to failure list */ private void addFailure(Xid xid, Uid uid) { if (_failures == null) _failures = new Hashtable(); Vector failureItem = (Vector) _failures.get(xid); if (failureItem == null) { failureItem = new Vector(); _failures.put(xid, failureItem); } failureItem.addElement(uid); } /* remove record uid from failure list */ private void removeFailure(Xid xid, Uid uid) { // find the failure item for this xid Vector failureItem = (Vector) _failures.get(xid); if (failureItem == null) { /* * if (jtaLogger.loggerI18N.isWarnEnabled()) { * jtaLogger.loggerI18N.warn("com.arjuna.ats.internal.jta.recovery.removefailed", * new Object[] { _logName, xid}); } */ /* * Already removed via previousFailure. */ } else { // remove this record from the item failureItem.remove(uid); // if that was the last one, remove the item altogether if (failureItem.size() == 0) _failures.remove(xid); } } private void clearAllFailures() { if (_failures != null) _failures.clear(); } /** * Check whether an XAResourceRecoveryHelper is currently being used by the scanner. * Must be called holding a lock on scanState * @param xaResourceRecoveryHelper the helper * @return true if the helper is in use */ private boolean isHelperInUse(XAResourceRecoveryHelper xaResourceRecoveryHelper) { XAResource[] xaResources = recoveryHelpersXAResource.get(xaResourceRecoveryHelper); if (xaResources != null) { for (int i = 0; i < xaResources.length; i++) { RecoveryXids recoveryXids = _xidScans.get(xaResources[i]); if (recoveryXids != null && recoveryXids.size() > 0) { return true; } } } return false; } /** * Wait until scanner reaches a specific target state. * Must be called holding a lock on scanState. * @param state the target scan state to wait for * @return false if the thread was interrupted */ private boolean waitForScanState(ScanStates state) { try { do { scanState.wait(); } while (!getScanState().equals(state)); return true; } catch (InterruptedException e) { tsLogger.logger.warn("problem waiting for scanLock whilst in state " + state.name(), e); return false; } } private boolean waitForNotScanState(ScanStates state) { try { while (getScanState().equals(state)) { scanState.wait(); } return true; } catch (InterruptedException e) { tsLogger.logger.warn("problem waiting for scanLock whilst in state " + state.name(), e); return false; } } /** * Update the status of the scanner * @param state the new state */ private void setScanState(ScanStates state) { synchronized (scanState) { tsLogger.logger.debugf("XARecoveryModule state change %s->%s%n", getScanState(), state); scanState.set(state.ordinal()); scanState.notifyAll(); } } private ScanStates getScanState() { return ScanStates.values()[scanState.get()]; } private void saveContactedJndiName(final XAResource xaResource) { if (!(xaResource instanceof XAResourceWrapper)) { return; } final String jndiName = ((XAResourceWrapper) xaResource).getJndiName(); if (jndiName != null && jndiName.length() > 0) { contactedJndiNames.add(jndiName); } } private RecoveryStore _recoveryStore = StoreManager.getRecoveryStore(); private InputObjectState _uids = new InputObjectState(); private List<XAResource> _resources; // WARNING com.hp.mwtests.ts.jta.recovery.XARecoveryModuleUnitTest uses reflection to peek at the scan state of this recovery module private enum ScanStates { IDLE, FIRST_PASS, BETWEEN_PASSES, SECOND_PASS } private AtomicInteger scanState = new AtomicInteger(ScanStates.IDLE.ordinal()); private final List<XAResourceRecovery> _xaRecoverers; private final Set<XAResourceRecoveryHelper> _xaResourceRecoveryHelpers = new CopyOnWriteArraySet<XAResourceRecoveryHelper>(); private final List<XAResourceOrphanFilter> _xaResourceOrphanFilters; private Hashtable _failures = null; private Hashtable<XAResourceRecoveryHelper,XAResource[]> recoveryHelpersXAResource = new Hashtable<XAResourceRecoveryHelper,XAResource[]>(); private Hashtable<XAResource,RecoveryXids> _xidScans = null; private XARecoveryResourceManager _recoveryManagerClass = null; private String _logName = null; private List<SerializableXAResourceDeserializer> _seriablizableXAResourceDeserializers = new ArrayList<SerializableXAResourceDeserializer>(); private Set<String> contactedJndiNames = new HashSet<String>(); private static XARecoveryModule registeredXARecoveryModule; }