From 25a7a06398e7b895c34243d48a5287d31b3ff76a Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 22 May 2024 13:33:34 +0200 Subject: [PATCH] Transaction manager should catch `Throwable` (#2954) --- .../AsyncUsingSyncTransactionOperations.java | 5 +- .../AbstractTransactionOperations.java | 2 +- .../example/PersonSuspendRepositoryService.kt | 22 ++++++++ .../src/test/kotlin/example/TxTest.kt | 52 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/data-tx/src/main/java/io/micronaut/transaction/async/AsyncUsingSyncTransactionOperations.java b/data-tx/src/main/java/io/micronaut/transaction/async/AsyncUsingSyncTransactionOperations.java index 27ac68a35b..7bf55cc382 100644 --- a/data-tx/src/main/java/io/micronaut/transaction/async/AsyncUsingSyncTransactionOperations.java +++ b/data-tx/src/main/java/io/micronaut/transaction/async/AsyncUsingSyncTransactionOperations.java @@ -47,13 +47,12 @@ public CompletionStage withTransaction(TransactionDefinition definition, Function, CompletionStage> handler) { CompletableFuture newResult = new CompletableFuture<>(); PropagatedContext propagatedContext = PropagatedContext.getOrEmpty(); - try (PropagatedContext.Scope scope = propagatedContext.propagate()) { // Propagate to clean up the scope + try (PropagatedContext.Scope ignore = propagatedContext.propagate()) { // Propagate to clean up the scope TransactionStatus status = synchronousTransactionManager.getTransaction(definition); - PropagatedContext txPropagatedContext = PropagatedContext.get(); CompletionStage result; try { result = handler.apply(new DefaultAsyncTransactionStatus<>(status)); - } catch (Exception e) { + } catch (Throwable e) { CompletableFuture r = new CompletableFuture<>(); r.completeExceptionally(e); result = r; diff --git a/data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java b/data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java index 38cc57e6fa..72b93101a2 100644 --- a/data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java +++ b/data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java @@ -331,7 +331,7 @@ private R executeTransactional(T transaction, TransactionCallback call R result; try { result = callback.apply(transaction); - } catch (Exception e) { + } catch (Throwable e) { if (definition.rollbackOn(e)) { rollbackInternal(transaction); } else { diff --git a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/PersonSuspendRepositoryService.kt b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/PersonSuspendRepositoryService.kt index 6634a482a7..5910002372 100644 --- a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/PersonSuspendRepositoryService.kt +++ b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/PersonSuspendRepositoryService.kt @@ -59,6 +59,28 @@ open class PersonSuspendRepositoryService(private val parentSuspendRepository: P throw RuntimeException("exception") } + @Transactional + open fun normalStoreThrowable() { + saveOne() + throw Throwable("exception") + } + + @Transactional + open fun normalThrowable() { + throw Throwable("exception") + } + + @Transactional + open suspend fun coroutinesStoreThrowable() { + saveOneSuspended() + throw Throwable("exception") + } + + @Transactional + open suspend fun coroutinesThrowable() { + throw Throwable("exception") + } + @Transactional open suspend fun coroutinesStore() { saveOneSuspended() diff --git a/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/TxTest.kt b/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/TxTest.kt index 22a1b28114..938b27f9b1 100644 --- a/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/TxTest.kt +++ b/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/TxTest.kt @@ -154,4 +154,56 @@ class TxTest { Assertions.assertEquals(0, service.countForCustomDb()) } + @Test + @Order(12) + fun storeThrowable() { + repeat(10000) { // Validate the connection is properly closed + assertThrows { + service.normalStoreThrowable() + } + + Assertions.assertEquals(0, service.count()) + } + } + + @Test + @Order(13) + fun throwable() { + repeat(10000) { // Validate the connection is properly closed + assertThrows { + service.normalThrowable() + } + + Assertions.assertEquals(0, service.count()) + } + } + + @Test + @Order(14) + fun coroutineStoreThrowable() { + runBlocking { + repeat(10000) { // Validate the connection is properly closed + assertThrows { + service.coroutinesStoreThrowable() + } + + Assertions.assertEquals(0, service.count()) + } + } + } + + @Test + @Order(15) + fun coroutineThrowable() { + runBlocking { + repeat(10000) { // Validate the connection is properly closed + assertThrows { + service.coroutinesThrowable() + } + + Assertions.assertEquals(0, service.count()) + } + } + } + }