This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-3799
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/CAUSEWAY-3799 by this push:
     new 647d557a6a CAUSEWAY-3799: adds a guard to attempt to forestall changes 
being made
647d557a6a is described below

commit 647d557a6af4192965acb8ae74b5b48ff93e303a
Author: Dan Haywood <[email protected]>
AuthorDate: Fri Jun 28 23:20:47 2024 +0100

    CAUSEWAY-3799: adds a guard to attempt to forestall changes being made
    
    once the original transaction is committed
---
 .../session/InteractionServiceDefault.java         | 32 ++++++++++++++----
 .../transaction/TransactionServiceSpring.java      | 39 ++++++++++------------
 2 files changed, 43 insertions(+), 28 deletions(-)

diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
index 79c6d2a935..b7536c6997 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
@@ -363,19 +363,39 @@ implements
     @SneakyThrows
     private void preInteractionClosed(final CausewayInteraction interaction) {
 
+        // a bit of a hacky guard
+        //
+        // the suspicion is that if a background command execution encounters 
a deadlock then (in
+        // CommandExecutorServiceDefault) the top-level xactn will end up 
being rolled back.
+        //
+        // we've seen additional changes being made in a new/implicit (?) 
xactn which furthermore are never committed;
+        // we end up with a connection back in Hikari's conn pool with open 
locks.  If those changes are as a
+        // result of the transaction having completed, then this original 
method would have resulted in a command
+        // being persisted
+        //
+        if(transactionServiceSpring.currentTransactionState().isComplete()) {
+            // hmm... someone went and completed (rolled back/committed) our 
transaction from us
+            //
+            log.warn("preInteractionClosed: skipping as a precaution because 
current transaction has been completed already");
+            return;
+        }
+
         Throwable flushException = null;
-        try {
-            val mustAbort = 
transactionServiceSpring.currentTransactionState().mustAbort();
-            if(!mustAbort) {
+        val mustAbort = 
transactionServiceSpring.currentTransactionState().mustAbort();
+        if(!mustAbort) {
+            try {
                 transactionServiceSpring.flushTransaction();
                 // publish only when flush was successful
                 completeAndPublishCurrentCommand();
+            } catch (Throwable e) {
+                //[CAUSEWAY-3262] if flush fails rethrow later, when 
interaction was closed ...
+                flushException = e;
+                transactionServiceSpring.requestRollback(interaction);
             }
-        } catch (Throwable e) {
-            //[CAUSEWAY-3262] if flush fails rethrow later, when interaction 
was closed ...
-            flushException = e;
         }
 
+        // will either rollback or commit the actual transaction depending 
upon whether
+        // anything has called setRollbackOnly so far.
         transactionServiceSpring.onClose(interaction);
 
         interactionScopeLifecycleHandler.onTopLevelInteractionPreDestroy(); // 
cleanup the InteractionScope (Spring scope)
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
index 0d5a71fe02..be2bcf3d85 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -37,7 +37,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.DefaultTransactionStatus;
 import org.springframework.transaction.support.TransactionSynchronization;
 import 
org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.transaction.support.TransactionTemplate;
@@ -117,11 +116,9 @@ implements
         Try<T> result = null;
 
         try {
-
             TransactionStatus txStatus = 
platformTransactionManager.getTransaction(def);
             registerTransactionSynchronizations(txStatus);
 
-
             result = Try.call(() -> {
                         final T callResult = callable.call();
 
@@ -135,22 +132,27 @@ implements
                     .mapFailure(ex->translateExceptionIfPossible(ex, 
platformTransactionManager));
 
             if(result.isFailure()) {
+                // if this is a nested transaction, then the javadoc says it 
will actually be just a call to
+                // setRollbackOnly.
                 platformTransactionManager.rollback(txStatus);
             } else {
                 platformTransactionManager.commit(txStatus);
             }
         } catch (Exception ex) {
 
-            return result!=null
-                    && result.isFailure()
-
-                    // return the original failure cause (originating from 
calling the callable)
-                    // (so we don't shadow the original failure)
-                    ? result
+            // return the original failure cause (originating from calling the 
callable)
+            // (so we don't shadow the original failure)
+            // return the failure we just caught
+            if (result != null && result.isFailure()) {
+                return result;
+            }
 
-                    // return the failure we just caught
-                    : Try.failure(translateExceptionIfPossible(ex, 
platformTransactionManager));
+            // otherwise, we thought we had a success, but now we have an 
exception thrown by either ,
+            // the call to rollback or commit above.  We don't need to do 
anything though; if either of
+            // rollback or commit encountered an exception, they will have 
implicitly called setRollbackOnly (if nested)
+            // or just rolled-back if top-level.
 
+            return Try.failure(translateExceptionIfPossible(ex, 
platformTransactionManager));
         }
 
         return result;
@@ -159,17 +161,10 @@ implements
 
     private void registerTransactionSynchronizations(final TransactionStatus 
txStatus) {
         if (TransactionSynchronizationManager.isSynchronizationActive()) {
-            if (txStatus instanceof DefaultTransactionStatus) {
-                
configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
-                        .values()
-                        .stream().filter(AopUtils::isAopProxy)  // only the 
proxies
-                        
.forEach(TransactionSynchronizationManager::registerSynchronization);
-            } else {
-                
configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
-                        .values()
-                        .stream().filter(AopUtils::isAopProxy)  // only the 
proxies
-                        
.forEach(TransactionSynchronizationManager::registerSynchronization);
-            }
+            
configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
+                    .values()
+                    .stream().filter(AopUtils::isAopProxy)  // only the proxies
+                    
.forEach(TransactionSynchronizationManager::registerSynchronization);
         }
     }
 

Reply via email to