/* * 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) 2001, * * Hewlett-Packard Arjuna Labs, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: TwoPhaseCoordinator.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.arjuna.coordinator; import java.util.*; import java.util.concurrent.*; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.logging.tsLogger; /** * Adds support for synchronizations to BasicAction. It does not change thread * associations either. It also allows any thread to terminate a transaction, * even if it is not the transaction that is marked as current for the thread * (unlike the BasicAction default). * * @author Mark Little (mark@arjuna.com) * @version $Id: TwoPhaseCoordinator.java 2342 2006-03-30 13:06:17Z $ * @since JTS 3.0. */ public class TwoPhaseCoordinator extends BasicAction implements Reapable { public TwoPhaseCoordinator () { } public TwoPhaseCoordinator (Uid id) { super(id); } public int start () { return start(BasicAction.Current()); } public int start (BasicAction parentAction) { if (parentAction != null) { if (typeOfAction() == ActionType.NESTED) parentAction.addChildAction(this); } return super.Begin(parentAction); } public int end (boolean report_heuristics) { int outcome; if (parent() != null) { parent().removeChildAction(this); } boolean canEnd = true; if(status() != ActionStatus.ABORT_ONLY || TxControl.isBeforeCompletionWhenRollbackOnly()) { canEnd = beforeCompletion(); } if (canEnd) { outcome = super.End(report_heuristics); } else outcome = super.Abort(); afterCompletion(outcome, report_heuristics); return outcome; } /** * If this method is called and a transaction is not in a status of RUNNING, * ABORT_ONLY or COMMITTING then do not call afterCompletion. * * A scenario where this may occur is if during the completion of a previous * transaction, a runtime exception is thrown from one of the AbstractRecords * methods. * * RuntimeExceptions are not part of the contract of the API and as such all we * can do is leave the transaction alone. */ public int cancel () { if (parent() != null) parent().removeChildAction(this); // beforeCompletion(); int outcome = super.Abort(true); if (outcome == ActionStatus.ABORTED) { afterCompletion(outcome); } return outcome; } public int addSynchronization (SynchronizationRecord sr) { if (sr == null) return AddOutcome.AR_REJECTED; int result = AddOutcome.AR_REJECTED; // only allow registration for top-level transactions. if (parent() != null) return AddOutcome.AR_REJECTED; switch (status()) { // https://jira.jboss.org/jira/browse/JBTM-608 case ActionStatus.RUNNING: case ActionStatus.PREPARING: { synchronized (this) { if (_synchs == null) { // Synchronizations should be stored (or at least iterated) in their natural order _synchs = new TreeSet<SynchronizationRecord>(); } } synchronized (_synchs) { if (runningSynchronizations != null) { if (executingInterposedSynchs && !sr.isInterposed()) return AddOutcome.AR_REJECTED; runningSynchronizations.add(synchronizationCompletionService.submit( new AsyncBeforeSynchronization(this, sr))); return AddOutcome.AR_ADDED; } // disallow addition of Synchronizations that would appear // earlier in sequence than any that has already been called // during the pre-commmit phase. This generic support is required for // JTA Synchronization ordering behaviour if(_currentRecord != null) { if(sr.compareTo(_currentRecord) != 1) { return AddOutcome.AR_REJECTED; } } // need to guard against synchs being added while we are performing beforeCompletion processing if (_synchs.add(sr)) { result = AddOutcome.AR_ADDED; } } } break; default: break; } return result; } private boolean asyncBeforeCompletion() { boolean problem = false; Collection<SynchronizationRecord> interposedSynchs = new ArrayList<SynchronizationRecord>(); synchronized (_synchs) { synchronizationCompletionService = TwoPhaseCommitThreadPool.getNewCompletionService(); runningSynchronizations = new ArrayList<Future<Boolean>>(_synchs.size()); for (SynchronizationRecord synchRecord : _synchs) { if (synchRecord.isInterposed()) interposedSynchs.add(synchRecord); else runningSynchronizations.add(synchronizationCompletionService.submit( new AsyncBeforeSynchronization(this, synchRecord))); } // any further additions to _synchs from here on can only be interposed synchronizations } try { int processed = 0; do { synchronized (_synchs) { if (processed == runningSynchronizations.size()) { if (executingInterposedSynchs || interposedSynchs.size() == 0) break; // all synchronizations have been executed // all non interposed synchronizations have been executed executingInterposedSynchs = true; processed = 0; runningSynchronizations.clear(); for (SynchronizationRecord synchRecord : interposedSynchs) { runningSynchronizations.add(synchronizationCompletionService.submit( new AsyncBeforeSynchronization(this, synchRecord))); } } } processed += 1; try { if (!synchronizationCompletionService.take().get()) problem = true; } catch (ExecutionException e) { if (_deferredThrowable == null) _deferredThrowable = e.getCause(); // the wrapper around the synchronization will already have logged the error problem = true; } catch (InterruptedException e) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_2(_currentRecord.toString(), e); problem = true; } } while (!problem); } finally { // if there was a problem then cancel any remaining synchronizations try { for (Future<Boolean> f : runningSynchronizations) f.cancel(false); // canceling a completed task is a null op } finally { runningSynchronizations.clear(); } } return !problem; } /** * @return <code>true</code> if the transaction is running, * <code>false</code> otherwise. */ public boolean running () { return (status() == ActionStatus.RUNNING || status() == ActionStatus.ABORT_ONLY); } /** * Overloads BasicAction.type() */ public String type () { return "/StateManager/BasicAction/AtomicAction/TwoPhaseCoordinator"; } /** * Get any Throwable that was caught during commit processing but not directly rethrown. * @return the Throwable, if any */ public Throwable getDeferredThrowable() { return _deferredThrowable; } protected TwoPhaseCoordinator (int at) { super(at); } protected TwoPhaseCoordinator (Uid u, int at) { super(u, at); } /** * Drive beforeCompletion participants. * * @return true if successful, false otherwise. */ protected boolean beforeCompletion () { boolean problem = false; synchronized (_syncLock) { if (!_beforeCalled) { _beforeCalled = true; /* * If we have a synchronization list then we must be top-level. */ if (_synchs == null) { /* * beforeCompletions already called. Assume everything is alright * to proceed to commit. The TM instance will flag the outcome. If * it's rolling back, then we'll get an exception. If it's committing * then we'll be blocked until the commit (assuming we're still the * slower thread). */ } else if (TxControl.asyncBeforeSynch && _synchs.size() > 1) { problem = !asyncBeforeCompletion(); } else { /* * We must always call afterCompletion() methods, so just catch (and * log) any exceptions/errors from beforeCompletion() methods. * * If one of the Syncs throws an error the Record wrapper returns false * and we will rollback. Hence we don't then bother to call beforeCompletion * on the remaining records (it's not done for rollabcks anyhow). * * Since Synchronizations may register other Synchronizations, we can't simply * iterate the collection. Instead we work from an ordered copy, which we periodically * check for freshness. The addSynchronization method uses _currentRecord to disallow * adding records in the part of the array we have already traversed, thus all * Synchronization will be called and the (jta only) rules on ordering of interposed * Synchronization will be respected. */ int lastIndexProcessed = -1; SynchronizationRecord[] copiedSynchs; // need to guard against synchs being added while we are performing beforeCompletion processing synchronized (_synchs) { copiedSynchs = (SynchronizationRecord[])_synchs.toArray(new SynchronizationRecord[] {}); } while( (lastIndexProcessed < _synchs.size()-1) && !problem) { synchronized (_synchs) { // if new Synchronization have been registered, refresh our copy of the collection: if(copiedSynchs.length != _synchs.size()) { copiedSynchs = (SynchronizationRecord[])_synchs.toArray(new SynchronizationRecord[] {}); } } lastIndexProcessed = lastIndexProcessed+1; _currentRecord = copiedSynchs[lastIndexProcessed]; try { problem = !_currentRecord.beforeCompletion(); // if something goes wrong, we can't just throw the exception, we need to continue to // complete the transaction. However, the exception may have interesting information that // we want later, so we keep a reference to it as well as logging it. } catch (Exception ex) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_2(_currentRecord.toString(), ex); if (_deferredThrowable == null) { _deferredThrowable = ex; } problem = true; } catch (Error er) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_2(_currentRecord.toString(), er); if (_deferredThrowable == null) { _deferredThrowable = er; } problem = true; } } } } if (problem && !preventCommit()) { /* * This should not happen. If it does, continue with commit * to tidy-up. */ tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_1(); } } return !problem; } protected boolean asyncAfterCompletion(int myStatus, boolean report_heuristics) { boolean problem = false; if (synchronizationCompletionService == null) { synchronizationCompletionService = TwoPhaseCommitThreadPool.getNewCompletionService(); } if (runningSynchronizations == null) { runningSynchronizations = new ArrayList<Future<Boolean>>(_synchs.size()); } // note there is no need to synchronize on _synchs since synchronizations cannot be registered once // the action has started to commit for (Iterator<SynchronizationRecord> i =_synchs.iterator(); i.hasNext(); ) { SynchronizationRecord synchRecord = i.next(); if (!report_heuristics && synchRecord instanceof HeuristicNotification) ((HeuristicNotification) synchRecord).heuristicOutcome(getHeuristicDecision()); if (synchRecord.isInterposed()) { // run interposed synchronizations first i.remove(); runningSynchronizations.add(synchronizationCompletionService.submit( new AsyncAfterSynchronization(this, synchRecord, myStatus))); } } int processed = 0; executingInterposedSynchs = true; while (true) { if (processed == runningSynchronizations.size()) { if (!executingInterposedSynchs || _synchs.size() == 0) break; // all synchronizations have been executed // all interposed synchronizations have been executed executingInterposedSynchs = false; processed = 0; runningSynchronizations.clear(); for (SynchronizationRecord synchRecord : _synchs) { runningSynchronizations.add(synchronizationCompletionService.submit( new AsyncAfterSynchronization(this, synchRecord, myStatus))); } _synchs.clear(); } processed += 1; try { if (!synchronizationCompletionService.take().get()) problem = true; } catch (InterruptedException e) { problem = true; } catch (ExecutionException e) { problem = true; } } return !problem; } /** * Drive afterCompletion participants. * * @param myStatus the outcome of the transaction (ActionStatus.COMMITTED or ActionStatus.ABORTED). * * @return true if successful, false otherwise. */ protected boolean afterCompletion (int myStatus) { return afterCompletion(myStatus, false); } /** * Drive afterCompletion participants. * * @param myStatus the outcome of the transaction (ActionStatus.COMMITTED or ActionStatus.ABORTED). * @param report_heuristics does the caller want to be informed about heurisitics at the point of invocation? * * @return true if successful, false otherwise. */ protected boolean afterCompletion (int myStatus, boolean report_heuristics) { if (myStatus == ActionStatus.RUNNING) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_3(); return false; } boolean problem = false; synchronized (_syncLock) { if (!_afterCalled) { _afterCalled = true; if (_synchs == null) { return !problem; } else if (TxControl.asyncAfterSynch && _synchs.size() > 1) { problem = asyncAfterCompletion(myStatus, report_heuristics); } else { // afterCompletions should run in reverse order compared to // beforeCompletions Stack stack = new Stack(); Iterator iterator = _synchs.iterator(); while(iterator.hasNext()) { stack.push(iterator.next()); } /* * Regardless of failures, we must tell all synchronizations what * happened. */ while(!stack.isEmpty()) { SynchronizationRecord record = (SynchronizationRecord)stack.pop(); /* * If the caller doesn't want to be informed of heuristics during completion * then it's possible the application (or admin) may still want to be informed. * So special participants can be registered with the transaction which are * triggered during the Synchronization phase and given the true outcome of * the transaction. We do not dictate a specific implementation for what these * participants do with the information (e.g., OTS allows for the CORBA Notification Service * to be used). */ if (!report_heuristics) { if (record instanceof HeuristicNotification) { ((HeuristicNotification) record).heuristicOutcome(getHeuristicDecision()); } } try { if (!record.afterCompletion(myStatus)) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_4(record.toString()); problem = true; } } catch (Exception ex) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_4a(record.toString(), ex); problem = true; } catch (Error er) { tsLogger.i18NLogger.warn_coordinator_TwoPhaseCoordinator_4b(record.toString(), er); problem = true; } } synchronized (_synchs) { // nulling _syncs causes concurrency problems, so dispose contents instead: _synchs.clear(); } } } } return !problem; } public java.util.Map<Uid, String> getSynchronizations() { java.util.Map<Uid, String> synchs = new java.util.HashMap<Uid, String> (); synchronized (this) { if (_synchs != null) { for (Object _synch : _synchs) { SynchronizationRecord synch = (SynchronizationRecord) _synch; synchs.put(synch.get_uid(), synch.toString()); } } } return synchs; } private SortedSet<SynchronizationRecord> _synchs; private List<Future<Boolean>> runningSynchronizations = null; private CompletionService<Boolean> synchronizationCompletionService = null; private boolean executingInterposedSynchs = false; private SynchronizationRecord _currentRecord; // the most recently processed Synchronization. private Throwable _deferredThrowable; private Object _syncLock = new Object(); private boolean _beforeCalled = false; private boolean _afterCalled = false; }