You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/07/31 12:20:20 UTC

[isis] branch master updated: ISIS-2794: fixes InteractionService to request a tx rollback in case the given callable or runnable throws

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 581cadb  ISIS-2794: fixes InteractionService to request a tx rollback in case the given callable or runnable throws
581cadb is described below

commit 581cadbe26e3abf12e27440739d7f3b625bf41bd
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sat Jul 31 14:20:06 2021 +0200

    ISIS-2794: fixes InteractionService to request a tx rollback in case the
    given callable or runnable throws
---
 .../services/iactnlayer/InteractionService.java    | 10 +--
 .../isis/applib/services/xactn/TransactionId.java  |  1 -
 .../services/xactn/TransactionalProcessor.java     | 12 ++--
 ...teractionAwareTransactionalBoundaryHandler.java | 77 +++++++++++++---------
 .../session/InteractionServiceDefault.java         | 45 +++++++++----
 .../transaction/TransactionServiceSpring.java      | 15 +++--
 .../publishing/PropertyPublishingTestAbstract.java |  2 +
 .../publishing/PublishingTestAbstract.java         |  5 ++
 ...xceptionTranslationTest_usingTransactional.java |  3 +-
 ...ctionRollbackTest_usingInteractionService.java} | 27 ++++----
 ...actionRollbackTest_usingTransactionService.java |  9 +--
 ...TransactionRollbackTest_usingTransactional.java |  7 +-
 .../publishing/PublishingTestFactoryAbstract.java  |  3 +-
 13 files changed, 132 insertions(+), 84 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/iactnlayer/InteractionService.java b/api/applib/src/main/java/org/apache/isis/applib/services/iactnlayer/InteractionService.java
index 7ede49d..95a4b8b 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/iactnlayer/InteractionService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/iactnlayer/InteractionService.java
@@ -134,8 +134,9 @@ public interface InteractionService extends InteractionLayerTracker {
     }
 
     /**
-     * Variant of {@link #run(InteractionContext, ThrowingRunnable)} that wraps the return value
-     * with a {@link Result}, also catching any exception, that might have occurred.
+     * Variant of {@link #run(InteractionContext, ThrowingRunnable)} that returns
+     * a {@link Result} of {@code Result<Void>},
+     * also catching any exception, that might have occurred.
      */
     default Result<Void> runAndCatch(
             final @NonNull InteractionContext interactionContext,
@@ -153,8 +154,9 @@ public interface InteractionService extends InteractionLayerTracker {
     }
 
     /**
-     * Variant of {@link #runAnonymous(ThrowingRunnable)} that wraps the return value
-     * with a {@link Result}, also catching any exception, that might have occurred.
+     * Variant of {@link #runAnonymous(ThrowingRunnable)} that returns
+     * a {@link Result} of {@code Result<Void>},
+     * also catching any exception, that might have occurred.
      */
     default Result<Void> runAnonymousAndCatch(
             final @NonNull ThrowingRunnable runnable) {
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
index b28e128..3ea93fd 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
@@ -20,7 +20,6 @@ package org.apache.isis.applib.services.xactn;
 
 import java.util.UUID;
 
-import org.apache.isis.applib.mixins.system.HasInteractionId;
 import org.apache.isis.applib.mixins.system.HasTransactionId;
 
 import lombok.Value;
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionalProcessor.java b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionalProcessor.java
index 96de969..46f436a 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionalProcessor.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionalProcessor.java
@@ -24,8 +24,8 @@ import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 
-import org.apache.isis.commons.functional.Result;
 import org.apache.isis.applib.services.iactnlayer.ThrowingRunnable;
+import org.apache.isis.commons.functional.Result;
 
 import lombok.val;
 
@@ -52,7 +52,7 @@ public interface TransactionalProcessor {
      * Runs given {@code runnable} with a transactional boundary, where the detailed transactional behavior
      * is governed by given {@link TransactionDefinition} {@code def}.
      */
-    default Result<Void> runTransactional(TransactionDefinition def, ThrowingRunnable runnable) {
+    default Result<Void> runTransactional(final TransactionDefinition def, final ThrowingRunnable runnable) {
         return callTransactional(def, ThrowingRunnable.toCallable(runnable));
     }
 
@@ -65,7 +65,7 @@ public interface TransactionalProcessor {
      * More fine grained control is given via {@link #callTransactional(TransactionDefinition, Callable)}
      * @return {@link Result} of calling given {@code callable}
      */
-    default <T> Result<T> callTransactional(Propagation propagation, Callable<T> callable) {
+    default <T> Result<T> callTransactional(final Propagation propagation, final Callable<T> callable) {
         val def = new DefaultTransactionDefinition();
         def.setPropagationBehavior(propagation.value());
         return callTransactional(def, callable);
@@ -78,7 +78,7 @@ public interface TransactionalProcessor {
      * More fine grained control is given via
      * {@link #runTransactional(TransactionDefinition, ThrowingRunnable)}
      */
-    default Result<Void> runTransactional(Propagation propagation, ThrowingRunnable runnable) {
+    default Result<Void> runTransactional(final Propagation propagation, final ThrowingRunnable runnable) {
         return callTransactional(propagation, ThrowingRunnable.toCallable(runnable));
     }
 
@@ -94,7 +94,7 @@ public interface TransactionalProcessor {
      * @param callable
      * @return {@link Result} of calling given {@code callable}
      */
-    default <T> Result<T> callWithinCurrentTransactionElseCreateNew(Callable<T> callable) {
+    default <T> Result<T> callWithinCurrentTransactionElseCreateNew(final Callable<T> callable) {
         val def = new DefaultTransactionDefinition();
         def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
         return callTransactional(def, callable);
@@ -106,7 +106,7 @@ public interface TransactionalProcessor {
      *
      * @param runnable
      */
-    default Result<Void> runWithinCurrentTransactionElseCreateNew(ThrowingRunnable runnable) {
+    default Result<Void> runWithinCurrentTransactionElseCreateNew(final ThrowingRunnable runnable) {
         return callWithinCurrentTransactionElseCreateNew(ThrowingRunnable.toCallable(runnable));
     }
 
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java
index d8c8056..11cd353 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java
@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
 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.TransactionTemplate;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
@@ -40,6 +41,7 @@ import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.core.interaction.session.IsisInteraction;
 
 import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.Value;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -54,7 +56,7 @@ public class InteractionAwareTransactionalBoundaryHandler {
     private final Can<PlatformTransactionManager> txManagers;
 
     @Inject
-    public InteractionAwareTransactionalBoundaryHandler(List<PlatformTransactionManager> txManagers) {
+    public InteractionAwareTransactionalBoundaryHandler(final List<PlatformTransactionManager> txManagers) {
         this.txManagers = Can.ofCollection(txManagers);
     }
 
@@ -71,7 +73,7 @@ public class InteractionAwareTransactionalBoundaryHandler {
         }
 
         val onCloseTasks = _Lists.<CloseTask>newArrayList(txManagers.size());
-        interaction.putAttribute(Handle.class, new Handle(onCloseTasks));
+        interaction.putAttribute(OnCloseHandle.class, new OnCloseHandle(onCloseTasks));
 
         txManagers.forEach(txManager->newTransactionOrParticipateInExisting(txManager, onCloseTasks::add));
 
@@ -89,32 +91,21 @@ public class InteractionAwareTransactionalBoundaryHandler {
             return; // nothing to do
         }
 
-        val onCloseTasks = Optional.ofNullable(interaction.getAttribute(Handle.class))
-                .map(Handle::getOnCloseTasks);
+        Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
+                .ifPresent(OnCloseHandle::runOnCloseTasks);
 
-        onCloseTasks
-        .ifPresent(tasks->tasks.forEach(onCloseTask->{
-
-            try {
-                onCloseTask.getRunnable().run();
-            } catch(final Throwable ex) {
-                // ignore
-                log.error(
-                        "failed to close transactional boundary using transaction-manager {}; "
-                        + "continuing to avoid memory leakage",
-                        onCloseTask.getOnErrorInfo(),
-                        ex);
-            }
-
-        }));
+    }
 
+    public void requestRollback(final @NonNull IsisInteraction interaction) {
+        Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
+                .ifPresent(OnCloseHandle::requestRollback);
     }
 
     // -- HELPER
 
     private void newTransactionOrParticipateInExisting(
             final PlatformTransactionManager txManager,
-            final Consumer<CloseTask> onCloseTaskCallback) {
+            final Consumer<CloseTask> onNewCloseTask) {
 
         val txTemplate = new TransactionTemplate(txManager);
         txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
@@ -129,28 +120,52 @@ public class InteractionAwareTransactionalBoundaryHandler {
 
         // we have created a new transaction, so need to provide a CloseTask
 
-        onCloseTaskCallback.accept(new CloseTask(
-                txManager.getClass().getName(), // info to be used for display in case of errors
-                ()->{
+        onNewCloseTask.accept(
+            new CloseTask(
+                    txStatus,
+                    txManager.getClass().getName(), // info to be used for display in case of errors
+                    ()->{
 
-                    if(txStatus.isRollbackOnly()) {
-                        txManager.rollback(txStatus);
-                    } else {
-                        txManager.commit(txStatus);
-                    }
+                        if(txStatus.isRollbackOnly()) {
+                            txManager.rollback(txStatus);
+                        } else {
+                            txManager.commit(txStatus);
+                        }
 
-                }));
+                    }));
     }
 
     @Value
     private static class CloseTask {
+        private final @NonNull TransactionStatus txStatus;
         private final @NonNull String onErrorInfo;
         private final @NonNull ThrowingRunnable runnable;
     }
 
-    @Value
-    private static class Handle {
+    @RequiredArgsConstructor
+    private static class OnCloseHandle {
         private final @NonNull List<CloseTask> onCloseTasks;
+        void requestRollback() {
+            onCloseTasks.forEach(onCloseTask->{
+                onCloseTask.txStatus.setRollbackOnly();
+            });
+        }
+        void runOnCloseTasks() {
+            onCloseTasks.forEach(onCloseTask->{
+
+                try {
+                    onCloseTask.getRunnable().run();
+                } catch(final Throwable ex) {
+                    // ignore
+                    log.error(
+                            "failed to close transactional boundary using transaction-manager {}; "
+                            + "continuing to avoid memory leakage",
+                            onCloseTask.getOnErrorInfo(),
+                            ex);
+                }
+
+            });
+        }
     }
 
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
index 54ce7ae..65ebdf8 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/session/InteractionServiceDefault.java
@@ -246,10 +246,8 @@ implements
 
         final int stackSizeWhenEntering = interactionLayerStack.get().size();
         openInteraction(interactionContext);
-
         try {
-            serviceInjector.injectServicesInto(callable);
-            return callable.call();
+            return callInternal(callable);
         } finally {
             closeInteractionLayerStackDownToStackSize(stackSizeWhenEntering);
         }
@@ -263,14 +261,11 @@ implements
 
         final int stackSizeWhenEntering = interactionLayerStack.get().size();
         openInteraction(interactionContext);
-
         try {
-            serviceInjector.injectServicesInto(runnable);
-            runnable.run();
+            runInternal(runnable);
         } finally {
             closeInteractionLayerStackDownToStackSize(stackSizeWhenEntering);
         }
-
     }
 
     // -- ANONYMOUS EXECUTION
@@ -279,8 +274,7 @@ implements
     @SneakyThrows
     public <R> R callAnonymous(@NonNull final Callable<R> callable) {
         if(isInInteraction()) {
-            serviceInjector.injectServicesInto(callable);
-            return callable.call(); // reuse existing session
+            return callInternal(callable); // participate in existing session
         }
         return call(InteractionContextFactory.anonymous(), callable);
     }
@@ -291,10 +285,9 @@ implements
      */
     @Override
     @SneakyThrows
-    public void runAnonymous(@NonNull final ThrowingRunnable runnable) {
+    public void runAnonymous(final @NonNull ThrowingRunnable runnable) {
         if(isInInteraction()) {
-            serviceInjector.injectServicesInto(runnable);
-            runnable.run(); // reuse existing session
+            runInternal(runnable); // participate in existing session
             return;
         }
         run(InteractionContextFactory.anonymous(), runnable);
@@ -311,6 +304,34 @@ implements
 
     // -- HELPER
 
+    @SneakyThrows
+    private <R> R callInternal(final @NonNull Callable<R> callable) {
+        serviceInjector.injectServicesInto(callable);
+        try {
+            return callable.call();
+        } catch (Exception e) {
+            requestRollback();
+            throw e;
+        }
+    }
+
+    @SneakyThrows
+    private void runInternal(final @NonNull ThrowingRunnable runnable) {
+        serviceInjector.injectServicesInto(runnable);
+        try {
+            runnable.run();
+        } catch (Exception e) {
+            requestRollback();
+            throw e;
+        }
+    }
+
+    private void requestRollback() {
+        val stack = interactionLayerStack.get();
+        val interaction = _Casts.<IsisInteraction>uncheckedCast(stack.get(0).getInteraction());
+        txBoundaryHandler.requestRollback(interaction);
+    }
+
     private boolean isAtTopLevel() {
     	return interactionLayerStack.get().size()==1;
     }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
index 904c984..11b14a4 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -97,15 +97,17 @@ implements
     @Override
     public <T> Result<T> callTransactional(final TransactionDefinition def, final Callable<T> callable) {
 
-        val txManager = transactionManagerForElseFail(def);
+        val txManager = transactionManagerForElseFail(def); // always throws if configuration is wrong
 
-        val tx = txManager.getTransaction(def);
-
-        val result = Result.of(callable)
-                .mapFailure(ex->translateExceptionIfPossible(ex, txManager));
+        Result<T> result = null;
 
         try {
 
+            val tx = txManager.getTransaction(def);
+
+            result = Result.of(callable)
+                    .mapFailure(ex->translateExceptionIfPossible(ex, txManager));
+
             if(result.isFailure()) {
                 txManager.rollback(tx);
             } else {
@@ -114,7 +116,8 @@ implements
 
         } catch (Exception ex) {
 
-            return result.isFailure()
+            return result!=null
+                    && result.isFailure()
 
                     // return the original failure cause (originating from calling the callable)
                     // (so we don't shadow the original failure)
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PropertyPublishingTestAbstract.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PropertyPublishingTestAbstract.java
index f5d1f08..65d5a7c 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PropertyPublishingTestAbstract.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PropertyPublishingTestAbstract.java
@@ -64,6 +64,8 @@ extends PublishingTestAbstract {
             switch(changeScenario) {
             case ENTITY_CREATION:
                 return; // factory-service does not trigger property publishing
+            case ENTITY_LOADING:
+                return; // not subject of change tests
             case ENTITY_PERSISTING:
                 assertContainsPropertyChangeEntries(Can.of(
                         formatPersistenceStandardSpecificCapitalize("%s Book/name: '[NEW]' -> 'Sample Book'")));
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
index 454607a..0489435 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
@@ -24,6 +24,11 @@ implements HasPersistenceStandard {
         return generateTests(ChangeScenario.ENTITY_CREATION);
     }
 
+    @TestFactory @DisplayName("Entity Persisting")
+    final List<DynamicTest> generateTestsForPersisting() {
+        return generateTests(ChangeScenario.ENTITY_PERSISTING);
+    }
+
     @TestFactory @DisplayName("Entity Loading")
     final List<DynamicTest> generateTestsForLoading() {
         return generateTests(ChangeScenario.ENTITY_LOADING);
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
index f9dece0..d4836ec 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
@@ -31,7 +31,6 @@ import org.junit.jupiter.api.TestMethodOrder;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.dao.DataIntegrityViolationException;
-import org.springframework.orm.jpa.JpaTransactionManager;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.TestPropertySource;
@@ -74,7 +73,7 @@ class JpaExceptionTranslationTest_usingTransactional
     @Inject private RepositoryService repositoryService;
     @Inject private InteractionService interactionService;
     @Inject private Provider<JpaInventoryDao> inventoryDao;
-    @Inject private JpaTransactionManager txManager;
+    //@Inject private JpaTransactionManager txManager;
 
     @BeforeAll
     static void beforeAll() throws SQLException {
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
similarity index 89%
copy from regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
copy to regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
index f63658a..8746594 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
@@ -35,11 +35,11 @@ import org.springframework.test.context.TestPropertySource;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.commons.internal.base._Refs;
 import org.apache.isis.commons.internal.base._Refs.ObjectReference;
-import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.transaction.events.TransactionAfterCompletionEvent;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
@@ -55,7 +55,7 @@ import lombok.val;
 @SpringBootTest(
         classes = {
                 Configuration_usingJpa.class,
-                JpaTransactionRollbackTest_usingTransactionService.CommitListener.class
+                JpaTransactionRollbackTest_usingInteractionService.CommitListener.class
         },
         properties = {
 //                "logging.level.org.springframework.test.context.transaction.*=DEBUG",
@@ -64,12 +64,13 @@ import lombok.val;
 @TestPropertySource(IsisPresets.UseLog4j2Test)
 @ExtendWith({IsisInteractionHandler.class})
 @DirtiesContext
-class JpaTransactionRollbackTest_usingTransactionService
+class JpaTransactionRollbackTest_usingInteractionService
 //extends IsisIntegrationTestAbstract
 {
 
     @Inject private FixtureScripts fixtureScripts;
     @Inject private TransactionService transactionService;
+    @Inject private InteractionService interactionService;
     @Inject private RepositoryService repository;
     @Inject private CommitListener commitListener;
 
@@ -96,7 +97,7 @@ class JpaTransactionRollbackTest_usingTransactionService
 
         commitListener.bind(transactionAfterCompletionEvent::set);
 
-        transactionService.runWithinCurrentTransactionElseCreateNew(()->{
+        interactionService.runAnonymous(()->{
 
             fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
         });
@@ -126,7 +127,7 @@ class JpaTransactionRollbackTest_usingTransactionService
 
         commitListener.bind(transactionAfterCompletionEvent::set);
 
-        val result = transactionService.runWithinCurrentTransactionElseCreateNew(()->{
+        val result = interactionService.runAnonymousAndCatch(()->{
 
             fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
 
@@ -157,33 +158,35 @@ class JpaTransactionRollbackTest_usingTransactionService
             assertEquals(0, repository.allInstances(JpaBook.class).size());
         });
 
-        _Probe.errOut("before outer tx");
+        //_Probe.errOut("before outer tx");
 
         commitListener.bind(transactionAfterCompletionEvent::set);
 
-        val result = transactionService.runWithinCurrentTransactionElseCreateNew(()->{
+        val result = interactionService.runAnonymousAndCatch(()->{
 
             //_Probe.errOut("before tx that should trigger a rollback");
 
-            transactionService.runWithinCurrentTransactionElseCreateNew(()->{
+            val innerResult = transactionService.runWithinCurrentTransactionElseCreateNew(()->{
 
                 fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
 
                 throw new RuntimeException("Test: force current tx to rollback");
             });
 
+            assertTrue(innerResult.isFailure());
+
             //_Probe.errOut("after tx that should have triggered a rollback");
 
         });
 
-        _Probe.errOut("after outer tx");
+        //_Probe.errOut("after outer tx");
 
-        assertTrue(result.isFailure());
+        // interactionService detects whether a rollback was requested and does not throw in such a case
+        assertTrue(result.isSuccess());
 
         val actualEvent = transactionAfterCompletionEvent.getValueElseDefault(null);
         assertTrue(
-                actualEvent == TransactionAfterCompletionEvent.ROLLED_BACK
-                    || actualEvent == TransactionAfterCompletionEvent.UNKNOWN);
+                actualEvent == TransactionAfterCompletionEvent.ROLLED_BACK);
 
         transactionService.runWithinCurrentTransactionElseCreateNew(()->{
 
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
index f63658a..27031b4 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
@@ -39,7 +39,6 @@ import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.commons.internal.base._Refs;
 import org.apache.isis.commons.internal.base._Refs.ObjectReference;
-import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.transaction.events.TransactionAfterCompletionEvent;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
@@ -157,7 +156,7 @@ class JpaTransactionRollbackTest_usingTransactionService
             assertEquals(0, repository.allInstances(JpaBook.class).size());
         });
 
-        _Probe.errOut("before outer tx");
+        //_Probe.errOut("before outer tx");
 
         commitListener.bind(transactionAfterCompletionEvent::set);
 
@@ -165,7 +164,7 @@ class JpaTransactionRollbackTest_usingTransactionService
 
             //_Probe.errOut("before tx that should trigger a rollback");
 
-            transactionService.runWithinCurrentTransactionElseCreateNew(()->{
+            val innerResult = transactionService.runWithinCurrentTransactionElseCreateNew(()->{
 
                 fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
 
@@ -174,9 +173,11 @@ class JpaTransactionRollbackTest_usingTransactionService
 
             //_Probe.errOut("after tx that should have triggered a rollback");
 
+            assertTrue(innerResult.isFailure());
+
         });
 
-        _Probe.errOut("after outer tx");
+        //_Probe.errOut("after outer tx");
 
         assertTrue(result.isFailure());
 
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactional.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactional.java
index b8ccbf0..29f4e24 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactional.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactional.java
@@ -33,7 +33,6 @@ import org.springframework.transaction.annotation.Transactional;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.isis.applib.services.repository.RepositoryService;
-import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.testdomain.conf.Configuration_usingJpa;
 import org.apache.isis.testdomain.jpa.JpaTestDomainPersona;
@@ -51,9 +50,9 @@ import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstra
                 Configuration_usingJpa.class,
         },
         properties = {
-                "logging.level.org.apache.isis.core.runtimeservices.session.InteractionFactoryDefault=DEBUG",
-                "logging.level.org.apache.isis.persistence.*=DEBUG",
-                "logging.level.org.springframework.test.context.transaction.*=DEBUG"
+//                "logging.level.org.apache.isis.core.runtimeservices.session.InteractionFactoryDefault=DEBUG",
+//                "logging.level.org.apache.isis.persistence.*=DEBUG",
+//                "logging.level.org.springframework.test.context.transaction.*=DEBUG"
         })
 @Transactional
 @TestPropertySource(IsisPresets.UseLog4j2Test)
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
index 5e58d80..a72a428 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
@@ -358,13 +358,12 @@ public abstract class PublishingTestFactoryAbstract {
 //                    return result;
 //                });
 
-                val result = getInteractionService().callAnonymous(()->{
+                val result = getInteractionService().runAnonymousAndCatch(()->{
                     val currentInteraction = getInteractionService().currentInteraction();
                     xrayEnterInteraction(currentInteraction);
 
                     try {
                         testRunner.run(testContext); // is allowed to throw
-                        return Result.success(true);
                     } finally {
                         xrayExitInteraction();
                     }