Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a temporary config property to allow multiple resources #40365

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/src/main/asciidoc/datasource.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,84 @@
----
====

[[datasource-multiple-single-transaction]]
=== Use multiple datasources in a single transaction

By default, XA support on datasources is disabled,

Check warning on line 523 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'therefore' rather than 'thus' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'therefore' rather than 'thus' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 523, "column": 46}}}, "severity": "WARNING"}
and thus a transaction may include at most one datasource.

Check warning on line 524 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 524, "column": 14}}}, "severity": "WARNING"}
Attempting to access multiple non-XA datasources in the same transaction
would result in an exception similar to this:

[source]
----
...
Caused by: java.sql.SQLException: Exception in association of connection to existing transaction
at io.agroal.narayana.NarayanaTransactionIntegration.associate(NarayanaTransactionIntegration.java:130)
...
Caused by: java.sql.SQLException: Unable to enlist connection to existing transaction
at io.agroal.narayana.NarayanaTransactionIntegration.associate(NarayanaTransactionIntegration.java:121)
...
----

To allow using multiple JDBC datasources in the same transaction:

Check warning on line 539 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 539, "column": 9}}}, "severity": "INFO"}

Check warning on line 539 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 539, "column": 60}}}, "severity": "WARNING"}

. Make sure your JDBC driver supports XA.
All <<extensions-and-database-drivers-reference,supported JDBC drivers do>>,
but <<other-databases,other JDBC drivers>> might not.

Check warning on line 543 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 543, "column": 47}}}, "severity": "WARNING"}
yrodiere marked this conversation as resolved.
Show resolved Hide resolved
. Make sure your database server is configured to enable XA.
. Enable XA support explicitly for each relevant datasource by setting
<<quarkus-agroal_quarkus-datasource-jdbc-transactions,`quarkus.datasource[.optional name].transactions`>> to `xa`.

Using XA, a rollback in one datasource will trigger a rollback in every other datasource enrolled in the transaction.

[NOTE]
====
XA transactions on reactive datasources are not supported at the moment.
====

[NOTE]
====
If your transaction involves other, non-datasource resources,

Check warning on line 557 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'remember' rather than 'keep in mind' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'remember' rather than 'keep in mind' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 557, "column": 53}}}, "severity": "WARNING"}
keep in mind *those* resources might not support XA transactions,
or might require additional configuration.
====

If XA cannot be enabled for one of your datasources:

* Be aware that enabling XA for all datasources _except one_ (and only one) is still supported
through https://www.narayana.io/docs/project/index.html#d5e857[Last Resource Commit Optimization (LRCO)].
* If you do not need a rollback for one datasource to trigger a rollback for other datasources,
consider splitting your code into multiple transactions.
To that end, use xref:transaction.adoc#programmatic-approach[`QuarkusTransaction.requiringNew()`]/xref:transaction.adoc#declarative-approach[`@Transactional(REQUIRES_NEW)`] (preferably)
or xref:transaction.adoc#legacy-api-approach[`UserTransaction`] (for more complex use cases).

[CAUTION]
====
As a last resort, and for compatibility with Quarkus 3.8 and earlier,

Check warning on line 573 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 573, "column": 65}}}, "severity": "WARNING"}
you may allow unsafe transaction handling across multiple non-XA datasources
by setting `quarkus.transaction-manager.unsafe-multiple-last-resources` to `allow`.

Check warning on line 575 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer. Raw Output: {"message": "[Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 575, "column": 76}}}, "severity": "INFO"}

With this property set to `allow`, a transaction rollback
could possibly be applied to only some of the non-XA datasources,
with other non-XA datasources having already committed their changes,
leaving your overall system in an inconsistent state.

Alternatively, you can allow the same unsafe behavior,
but with warnings when it is taken advantage of:

* setting the property to `warn-each`
would result in logging a warning on *each* offending transaction.
* setting the property to `warn-first`
would result in logging a warning on the *first* offending transaction.

We do not recommend using this configuration property,

Check warning on line 590 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 590, "column": 1}}}, "severity": "INFO"}

Check warning on line 590 in docs/src/main/asciidoc/datasource.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/datasource.adoc", "range": {"start": {"line": 590, "column": 10}}}, "severity": "INFO"}
and we plan to remove it in the future,
so you should plan fixing your application accordingly.
If you think your use case of this feature is valid and this option should be kept around,
open an issue in the https://github.com/quarkusio/quarkus/issues/new?assignees=&labels=kind%2Fenhancement&projects=&template=feature_request.yml[Quarkus tracker]
explaining why.
====

== Datasource integrations

=== Datasource health check
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.narayana.jta.deployment;

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -47,6 +48,8 @@
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -56,16 +59,21 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.narayana.jta.runtime.NarayanaJtaProducers;
import io.quarkus.narayana.jta.runtime.NarayanaJtaRecorder;
import io.quarkus.narayana.jta.runtime.TransactionManagerBuildTimeConfig;
import io.quarkus.narayana.jta.runtime.TransactionManagerBuildTimeConfig.UnsafeMultipleLastResourcesMode;
import io.quarkus.narayana.jta.runtime.TransactionManagerConfiguration;
import io.quarkus.narayana.jta.runtime.context.TransactionContext;
import io.quarkus.narayana.jta.runtime.graal.DisableLoggingFeature;
import io.quarkus.narayana.jta.runtime.interceptor.TestTransactionInterceptor;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorMandatory;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorNever;
Expand Down Expand Up @@ -93,7 +101,8 @@ public void build(NarayanaJtaRecorder recorder,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<RuntimeInitializedClassBuildItem> runtimeInit,
BuildProducer<FeatureBuildItem> feature,
TransactionManagerConfiguration transactions, ShutdownContextBuildItem shutdownContextBuildItem) {
TransactionManagerConfiguration transactions, TransactionManagerBuildTimeConfig transactionManagerBuildTimeConfig,
ShutdownContextBuildItem shutdownContextBuildItem) {
recorder.handleShutdown(shutdownContextBuildItem, transactions);
feature.produce(new FeatureBuildItem(Feature.NARAYANA_JTA));
additionalBeans.produce(new AdditionalBeanBuildItem(NarayanaJtaProducers.class));
Expand Down Expand Up @@ -137,13 +146,20 @@ public void build(NarayanaJtaRecorder recorder,
builder.addBeanClass(TransactionalInterceptorNotSupported.class);
additionalBeans.produce(builder.build());

transactionManagerBuildTimeConfig.unsafeMultipleLastResources.ifPresent(mode -> {
if (!mode.equals(UnsafeMultipleLastResourcesMode.FAIL)) {
recorder.logUnsafeMultipleLastResourcesOnStartup(mode);
}
});

//we want to force Arjuna to init at static init time
Properties defaultProperties = PropertiesFactory.getDefaultProperties();
//we don't want to store the system properties here
//we re-apply them at runtime
for (Object i : System.getProperties().keySet()) {
defaultProperties.remove(i);
}

recorder.setDefaultProperties(defaultProperties);
// This must be done before setNodeName as the code in setNodeName will create a TSM based on the value of this property
recorder.disableTransactionStatusManager();
Expand All @@ -152,6 +168,50 @@ public void build(NarayanaJtaRecorder recorder,
recorder.setConfig(transactions);
}

@BuildStep
@Record(STATIC_INIT)
public void allowUnsafeMultipleLastResources(NarayanaJtaRecorder recorder,
TransactionManagerBuildTimeConfig transactionManagerBuildTimeConfig,
Capabilities capabilities, BuildProducer<LogCleanupFilterBuildItem> logCleanupFilters,
BuildProducer<NativeImageFeatureBuildItem> nativeImageFeatures) {
switch (transactionManagerBuildTimeConfig.unsafeMultipleLastResources
.orElse(UnsafeMultipleLastResourcesMode.DEFAULT)) {
case ALLOW -> {
recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), true);
// we will handle the warnings ourselves at runtime init when the option is set explicitly
logCleanupFilters.produce(
new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012141", "ARJUNA012142"));
}
case WARN_FIRST -> {
recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), true);
// we will handle the warnings ourselves at runtime init when the option is set explicitly
// but we still want Narayana to produce a warning on the first offending transaction
logCleanupFilters.produce(
new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012142"));
}
case WARN_EACH -> {
recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), false);
// we will handle the warnings ourselves at runtime init when the option is set explicitly
// but we still want Narayana to produce one warning per offending transaction
logCleanupFilters.produce(
new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012142"));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsmet I think you can probably implement some sort of warn-once like this:

Suggested change
}
}
case WARN_ONCE -> {
recorder.allowUnsafeMultipleLastResources(capabilities.isPresent(Capability.AGROAL), true);
// we will handle the warnings ourselves at runtime init when the option is set explicitly
// but we still want Narayana to produce one warning on the first offending transaction
logCleanupFilters.produce(
new LogCleanupFilterBuildItem("com.arjuna.ats.arjuna", "ARJUNA012139", "ARJUNA012142"));
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think warning for each transaction will be problematic as you will fill the logs with warnings. I'm not sure this can fly in production (so I'm not sure it should be the default in production).

case FAIL -> { // No need to do anything, this is the default behavior of Narayana
}
}
}

@BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
public void nativeImageFeature(TransactionManagerBuildTimeConfig transactionManagerBuildTimeConfig,
BuildProducer<NativeImageFeatureBuildItem> nativeImageFeatures) {
switch (transactionManagerBuildTimeConfig.unsafeMultipleLastResources
.orElse(UnsafeMultipleLastResourcesMode.DEFAULT)) {
case ALLOW, WARN_FIRST, WARN_EACH -> {
nativeImageFeatures.produce(new NativeImageFeatureBuildItem(DisableLoggingFeature.class));
}
}
}

@BuildStep
@Record(RUNTIME_INIT)
@Consume(NarayanaInitBuildItem.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.runtime.util.StringUtil;

@Recorder
public class NarayanaJtaRecorder {
Expand Down Expand Up @@ -110,6 +111,30 @@ public void setConfig(final TransactionManagerConfiguration transactions) {
.setXaResourceOrphanFilterClassNames(transactions.xaResourceOrphanFilters);
}

/**
* This should be removed in the future.
*/
@Deprecated(forRemoval = true)
public void allowUnsafeMultipleLastResources(boolean agroalPresent, boolean disableMultipleLastResourcesWarning) {
arjPropertyManager.getCoreEnvironmentBean().setAllowMultipleLastResources(true);
arjPropertyManager.getCoreEnvironmentBean().setDisableMultipleLastResourcesWarning(disableMultipleLastResourcesWarning);
if (agroalPresent) {
jtaPropertyManager.getJTAEnvironmentBean()
.setLastResourceOptimisationInterfaceClassName("io.agroal.narayana.LocalXAResource");
}
}

/**
* This should be removed in the future.
*/
@Deprecated(forRemoval = true)
public void logUnsafeMultipleLastResourcesOnStartup(
TransactionManagerBuildTimeConfig.UnsafeMultipleLastResourcesMode mode) {
log.warnf(
"Setting quarkus.transaction-manager.unsafe-multiple-last-resources to '%s' makes adding multiple resources to the same transaction unsafe.",
StringUtil.hyphenate(mode.name()).replace('_', '-'));
}

private void setObjectStoreDir(String name, TransactionManagerConfiguration config) {
BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, name).setObjectStoreDir(config.objectStore.directory);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.narayana.jta.runtime;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public final class TransactionManagerBuildTimeConfig {
/**
* Define the behavior when using multiple XA unaware resources in the same transactional demarcation.
* <p>
* Defaults to {@code fail}.
* {@code warn} and {@code allow} are UNSAFE and should only be used for compatibility.
* Either use XA for all resources if you want consistency, or split the code into separate
* methods with separate transactions.
* <p>
* Note that using a single XA unaware resource together with XA aware resources, known as
* the Last Resource Commit Optimization (LRCO), is different from using multiple XA unaware
* resources. Although LRCO allows most transactions to complete normally, some errors can
* cause an inconsistent transaction outcome. Using multiple XA unaware resources is not
* recommended since the probability of inconsistent outcomes is significantly higher and
* much harder to recover from than LRCO. For this reason, use LRCO as a last resort.
* <p>
* We do not recommend using this configuration property, and we plan to remove it in the future,
* so you should plan fixing your application accordingly.
* If you think your use case of this feature is valid and this option should be kept around,
yrodiere marked this conversation as resolved.
Show resolved Hide resolved
* open an issue in our tracker explaining why.
*
* @deprecated This property is planned for removal in a future version.
*/
@Deprecated(forRemoval = true)
yrodiere marked this conversation as resolved.
Show resolved Hide resolved
@ConfigItem(defaultValueDocumentation = "fail")
public Optional<UnsafeMultipleLastResourcesMode> unsafeMultipleLastResources;

public enum UnsafeMultipleLastResourcesMode {
/**
* Allow using multiple XA unaware resources in the same transactional demarcation.
* <p>
* This will log a warning once on application startup,
* but not on each use of multiple XA unaware resources in the same transactional demarcation.
*/
ALLOW,
/**
* Allow using multiple XA unaware resources in the same transactional demarcation,
* but log a warning on the first occurrence.
*/
WARN_FIRST,
/**
* Allow using multiple XA unaware resources in the same transactional demarcation,
* but log a warning on each occurrence.
*/
WARN_EACH,
/**
* Allow using multiple XA unaware resources in the same transactional demarcation,
* but log a warning on each occurrence.
*/
FAIL;

// The default is WARN in Quarkus 3.8, FAIL in Quarkus 3.9+
// Make sure to update defaultValueDocumentation on unsafeMultipleLastResources when changing this.
public static final UnsafeMultipleLastResourcesMode DEFAULT = FAIL;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.quarkus.narayana.jta.runtime.graal;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.graalvm.nativeimage.hosted.Feature;

/**
* Disables logging during the analysis phase
*/
public class DisableLoggingFeature implements Feature {

private static final String[] CATEGORIES = {
"com.arjuna.ats.arjuna"
};

private final Map<String, Level> categoryMap = new HashMap<>(CATEGORIES.length);

@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
for (String category : CATEGORIES) {
Logger logger = Logger.getLogger(category);
categoryMap.put(category, logger.getLevel());
logger.setLevel(Level.SEVERE);
}
}

@Override
public void afterAnalysis(AfterAnalysisAccess access) {
for (String category : CATEGORIES) {
Level level = categoryMap.remove(category);
Logger logger = Logger.getLogger(category);
logger.setLevel(level);
}
}

@Override
public String getDescription() {
return "Disables INFO and WARN logging during the analysis phase";
}
}
Loading