-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17357 from marcelhanser/configuration-transactions
Configure transaction timeouts via properties
- Loading branch information
Showing
6 changed files
with
204 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,14 @@ | |
|
||
import java.io.Serializable; | ||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.Method; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletionException; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Function; | ||
|
||
import javax.inject.Inject; | ||
import javax.interceptor.InvocationContext; | ||
|
@@ -15,6 +19,8 @@ | |
import javax.transaction.TransactionManager; | ||
import javax.transaction.Transactional; | ||
|
||
import org.eclipse.microprofile.config.ConfigProvider; | ||
import org.jboss.logging.Logger; | ||
import org.jboss.tm.usertx.client.ServerVMClientUserTransaction; | ||
import org.reactivestreams.Publisher; | ||
|
||
|
@@ -27,13 +33,11 @@ | |
import io.smallrye.reactive.converters.ReactiveTypeConverter; | ||
import io.smallrye.reactive.converters.Registry; | ||
|
||
/** | ||
* @author [email protected] 02/05/2013 | ||
*/ | ||
|
||
public abstract class TransactionalInterceptorBase implements Serializable { | ||
|
||
private static final long serialVersionUID = 1L; | ||
private static final Logger log = Logger.getLogger(TransactionalInterceptorBase.class); | ||
private final Map<Method, Integer> methodTransactionTimeoutDefinedByPropertyCache = new ConcurrentHashMap<>(); | ||
|
||
@Inject | ||
TransactionManager transactionManager; | ||
|
@@ -66,8 +70,7 @@ public Object intercept(InvocationContext ic) throws Exception { | |
* Method handles CDI types to cover cases where extensions are used. In | ||
* case of EE container uses reflection. | ||
* | ||
* @param ic | ||
* invocation context of the interceptor | ||
* @param ic invocation context of the interceptor | ||
* @return instance of {@link Transactional} annotation or null | ||
*/ | ||
private Transactional getTransactional(InvocationContext ic) { | ||
|
@@ -104,18 +107,20 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm) thro | |
protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, RunnableWithException afterEndTransaction) | ||
throws Exception { | ||
|
||
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic); | ||
int timeoutConfiguredForMethod = getTransactionTimeoutFromAnnotation(ic); | ||
|
||
int currentTmTimeout = ((CDIDelegatingTransactionManager) transactionManager).getTransactionTimeout(); | ||
if (configAnnotation != null && configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT) { | ||
tm.setTransactionTimeout(configAnnotation.timeout()); | ||
|
||
if (timeoutConfiguredForMethod > 0) { | ||
tm.setTransactionTimeout(timeoutConfiguredForMethod); | ||
} | ||
|
||
Transaction tx; | ||
try { | ||
tm.begin(); | ||
tx = tm.getTransaction(); | ||
} finally { | ||
if (configAnnotation != null && configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT) { | ||
//restore the default behaviour | ||
if (timeoutConfiguredForMethod > 0) { | ||
tm.setTransactionTimeout(currentTmTimeout); | ||
} | ||
} | ||
|
@@ -167,6 +172,51 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, Runn | |
return ret; | ||
} | ||
|
||
private int getTransactionTimeoutFromAnnotation(InvocationContext ic) { | ||
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic); | ||
|
||
if (configAnnotation == null) { | ||
return -1; | ||
} | ||
|
||
int transactionTimeout = -1; | ||
|
||
if (!configAnnotation.timeoutFromConfigProperty().equals(TransactionConfiguration.UNSET_TIMEOUT_CONFIG_PROPERTY)) { | ||
Integer timeoutForMethod = methodTransactionTimeoutDefinedByPropertyCache.get(ic.getMethod()); | ||
if (timeoutForMethod != null) { | ||
transactionTimeout = timeoutForMethod; | ||
} else { | ||
transactionTimeout = methodTransactionTimeoutDefinedByPropertyCache.computeIfAbsent(ic.getMethod(), | ||
new Function<Method, Integer>() { | ||
@Override | ||
public Integer apply(Method m) { | ||
return TransactionalInterceptorBase.this.getTransactionTimeoutPropertyValue(configAnnotation); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
if (transactionTimeout == -1 && (configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT)) { | ||
transactionTimeout = configAnnotation.timeout(); | ||
} | ||
|
||
return transactionTimeout; | ||
} | ||
|
||
private Integer getTransactionTimeoutPropertyValue(TransactionConfiguration configAnnotation) { | ||
Optional<Integer> configTimeout = ConfigProvider.getConfig() | ||
.getOptionalValue(configAnnotation.timeoutFromConfigProperty(), Integer.class); | ||
if (configTimeout.isEmpty()) { | ||
if (log.isDebugEnabled()) { | ||
log.debugf("Configuration property '%s' was not provided, so it will not affect the transaction's timeout.", | ||
configAnnotation.timeoutFromConfigProperty()); | ||
} | ||
return -1; | ||
} | ||
|
||
return configTimeout.get(); | ||
} | ||
|
||
protected Object handleAsync(TransactionManager tm, Transaction tx, InvocationContext ic, Object ret, | ||
RunnableWithException afterEndTransaction) throws Exception { | ||
// Suspend the transaction to remove it from the main request thread | ||
|
@@ -263,7 +313,9 @@ protected Object invokeInNoTx(InvocationContext ic) throws Exception { | |
|
||
private void checkConfiguration(InvocationContext ic) { | ||
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic); | ||
if (configAnnotation != null && configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT) { | ||
if (configAnnotation != null && ((configAnnotation.timeout() != TransactionConfiguration.UNSET_TIMEOUT) | ||
|| !TransactionConfiguration.UNSET_TIMEOUT_CONFIG_PROPERTY | ||
.equals(configAnnotation.timeoutFromConfigProperty()))) { | ||
throw new RuntimeException("Changing timeout via @TransactionConfiguration can only be done " + | ||
"at the entry level of a transaction"); | ||
} | ||
|
@@ -333,7 +385,7 @@ protected void resetUserTransactionAvailability(boolean previousUserTransactionA | |
* An utility method to throw any exception as a {@link RuntimeException}. | ||
* We may throw a checked exception (subtype of {@code Throwable} or {@code Exception}) as un-checked exception. | ||
* This considers the Java 8 inference rule that states that a {@code throws E} is inferred as {@code RuntimeException}. | ||
* | ||
* <p> | ||
* This method can be used in {@code throw} statement such as: {@code throw sneakyThrow(exception);}. | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
integration-tests/jpa-h2/src/main/resources/application.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
quarkus.datasource.jdbc.max-size=8 | ||
dummy.transaction.timeout=30 | ||
transaction.timeout.1s=1 |
51 changes: 51 additions & 0 deletions
51
integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/DummyTransactionalService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package io.quarkus.it.jpa.h2; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.transaction.Transactional; | ||
|
||
import io.quarkus.narayana.jta.runtime.TransactionConfiguration; | ||
import io.quarkus.narayana.jta.runtime.interceptor.RunnableWithException; | ||
import io.quarkus.test.junit.QuarkusTest; | ||
|
||
@ApplicationScoped | ||
@QuarkusTest | ||
public class DummyTransactionalService { | ||
@Transactional | ||
public void doWithinPlainTransactional(RunnableWithException todo) throws Exception { | ||
todo.run(); | ||
} | ||
|
||
@Transactional | ||
@TransactionConfiguration(timeoutFromConfigProperty = "transaction.timeout.unknown") | ||
public void doWithinTransactionWithMissingProperty(RunnableWithException todo) throws Exception { | ||
todo.run(); | ||
} | ||
|
||
@Transactional | ||
@TransactionConfiguration(timeoutFromConfigProperty = "transaction.timeout.unknown", timeout = 2) | ||
public void doWithinTransactionWithMissingPropertyAndSetTimeoutInAnnotation(RunnableWithException todo) | ||
throws Exception { | ||
todo.run(); | ||
} | ||
|
||
@Transactional | ||
@TransactionConfiguration(timeoutFromConfigProperty = "transaction.timeout.1s", timeout = 2) | ||
public void doWithinTransactionWithPropertyAndSetTimeoutInAnnotation(RunnableWithException todo) | ||
throws Exception { | ||
todo.run(); | ||
} | ||
|
||
@Transactional | ||
@TransactionConfiguration(timeoutFromConfigProperty = "transaction.timeout.1s") | ||
public void doWithinTransactionWithProperty(RunnableWithException todo) | ||
throws Exception { | ||
todo.run(); | ||
} | ||
|
||
@Transactional | ||
@TransactionConfiguration() | ||
public void doWithinTransactionWithNothing(RunnableWithException todo) | ||
throws Exception { | ||
todo.run(); | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
...gration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TransactionConfigurationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package io.quarkus.it.jpa.h2; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import javax.inject.Inject; | ||
import javax.transaction.RollbackException; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.quarkus.narayana.jta.runtime.interceptor.RunnableWithException; | ||
import io.quarkus.test.junit.QuarkusTest; | ||
|
||
@QuarkusTest | ||
/** | ||
* Timeouts: | ||
* | ||
*/ | ||
public class TransactionConfigurationTest { | ||
@Inject | ||
DummyTransactionalService dummyService; | ||
|
||
@Test | ||
public void testMissingTransactionConfigurationPropertyIsIgnoredAndDefaultTimeoutIsUsed() throws Exception { | ||
dummyService.doWithinTransactionWithMissingProperty(someTaskTakingABitLongerThan1Second()); | ||
dummyService.doWithinTransactionWithMissingProperty(someTaskTakingABitLongerThan2Second()); | ||
} | ||
|
||
@Test | ||
public void testMissingTransactionConfigurationPropertyIsIgnoredAndAnnotationsTimeoutIsUsed() throws Exception { | ||
dummyService.doWithinTransactionWithMissingPropertyAndSetTimeoutInAnnotation(someTaskTakingABitLongerThan1Second()); | ||
assertThrows(RollbackException.class, () -> dummyService | ||
.doWithinTransactionWithMissingPropertyAndSetTimeoutInAnnotation(someTaskTakingABitLongerThan2Second())); | ||
} | ||
|
||
@Test | ||
public void testTransactionConfigurationPropertyOverridesAnnotationsTimeout() { | ||
assertThrows(RollbackException.class, () -> dummyService | ||
.doWithinTransactionWithPropertyAndSetTimeoutInAnnotation(someTaskTakingABitLongerThan1Second())); | ||
} | ||
|
||
@Test | ||
public void testTransactionConfigurationPropertyOverridesDefaultTimeout() { | ||
assertThrows(RollbackException.class, () -> dummyService | ||
.doWithinTransactionWithProperty(someTaskTakingABitLongerThan1Second())); | ||
} | ||
|
||
@Test | ||
public void testTransactionConfigurationCheckFailsIfTransactionConfigurationIsNotUsedAtTransactionEntryLevel() { | ||
assertThrows(RuntimeException.class, () -> dummyService | ||
.doWithinPlainTransactional(() -> dummyService.doWithinTransactionWithProperty(() -> { | ||
}))); | ||
} | ||
|
||
@Test | ||
public void testTransactionConfigurationCheckSucceedsIfTransactionConfigurationIsNotUsedAtTransactionEntryLevelButHasNoValuesSet() | ||
throws Exception { | ||
dummyService | ||
.doWithinPlainTransactional(() -> dummyService.doWithinTransactionWithNothing(() -> { | ||
})); | ||
} | ||
|
||
private RunnableWithException someTaskTakingABitLongerThan1Second() { | ||
return () -> Thread.sleep(1100); | ||
} | ||
|
||
private RunnableWithException someTaskTakingABitLongerThan2Second() { | ||
return () -> Thread.sleep(2100); | ||
} | ||
} |