/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.resource.transaction.backend.jta.internal.synchronization; import org.hibernate.HibernateException; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.internal.CoreMessageLogger; import static org.hibernate.internal.CoreLogging.messageLogger; /** * Extension of SynchronizationCallbackCoordinatorNonTrackingImpl that adds checking of whether a rollback comes from * a thread other than the application thread (thread used to register the Synchronization) * * @author Steve Ebersole * @author Brett Meyer */ public class SynchronizationCallbackCoordinatorTrackingImpl extends SynchronizationCallbackCoordinatorNonTrackingImpl { private static final CoreMessageLogger log = messageLogger( SynchronizationCallbackCoordinatorTrackingImpl.class ); private volatile long registrationThreadId; private volatile boolean delayedCompletionHandling; public SynchronizationCallbackCoordinatorTrackingImpl(SynchronizationCallbackTarget target) { // super ctor calls reset() followed by pulse() super( target ); } @Override public void reset() { super.reset(); // NOTE : reset is typically called: // 1) on initialization, and // 2) after "after completion" handling is finished. // // Here we use that to "clear out" all 'delayed after-completion" state. The registrationThreadId will // "lazily" be re-populated on the next synchronizationRegistered call to allow for the potential of the // next Session transaction occurring on a different thread (though that transaction would need to completely // operate on that thread). delayedCompletionHandling = false; } @Override public void afterCompletion(int status) { log.tracef( "Synchronization coordinator: afterCompletion(status=%s)", status ); // The whole concept of "tracking" comes down to this code block.. // Essentially we need to see if we can process the callback immediately. So here we check whether the // current call is happening on the same thread as the thread under which we registered the Synchronization. // As far as we know, this can only ever happen in the rollback case where the transaction had been rolled // back on a separate "reaper" thread. Since we know the transaction status and that check is not as heavy // as accessing the current thread, we check that first if ( JtaStatusHelper.isRollback( status ) ) { // we are processing a rollback, see if it is the same thread final long currentThreadId = Thread.currentThread().getId(); final boolean isRegistrationThread = currentThreadId == registrationThreadId; if ( ! isRegistrationThread ) { // so we do have the condition of a rollback initiated from a separate thread. Set the flag here and // check for it in SessionImpl. See HHH-7910. delayedCompletionHandling = true; log.rollbackFromBackgroundThread( status ); return; } } // otherwise, do the callback immediately doAfterCompletion( JtaStatusHelper.isCommitted( status ), false ); } @Override public void synchronizationRegistered() { registrationThreadId = Thread.currentThread().getId(); } @Override public void processAnyDelayedAfterCompletion() { if ( delayedCompletionHandling ) { delayedCompletionHandling = false; // false here (rather than how we used to keep and check the status) because as discussed above // the delayed logic should only ever occur during rollback doAfterCompletion( false, true ); // NOTE : doAfterCompletion calls reset throw new HibernateException( "Transaction was rolled back in a different thread!" ); } } }