Skip to content

Commit

Permalink
Merge pull request #45301 from gsmet/prevent-oracle-closed-connection…
Browse files Browse the repository at this point in the history
…-commit

Prevent Oracle from committing uncommitted transactions on connection close
  • Loading branch information
gsmet authored Jan 8, 2025
2 parents 3119eff + 44fd4b3 commit 757a4d4
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 0 deletions.
13 changes: 13 additions & 0 deletions docs/src/main/asciidoc/datasource.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,19 @@ AgroalDataSource defaultDataSource;
In the above example, the type is `AgroalDataSource`, a `javax.sql.DataSource` subtype.
Because of this, you can also use `javax.sql.DataSource` as the injected type.

===== Oracle considerations

As documented in https://github.com/quarkusio/quarkus/issues/36265[issue #36265],
Oracle has a very weird behavior of committing the uncommitted transactions on connection closing.

Which means that when stopping Quarkus for instance, in progress transactions might be committed even if incomplete.

Given that is not the expected behavior and that it could lead to data loss, we added an interceptor that rolls back any unfinished transactions at connection close,
provided you are not using XA (in which case the transaction manager handles things for you).

If this behavior introduced in 3.18 causes issues for your specific workload, you can disable it by setting the `-Dquarkus-oracle-no-automatic-rollback-on-connection-close` system property to `true`.
Please take the time to report your use case in our https://github.com/quarkusio/quarkus/issues[issue tracker] so that we can adjust this behavior if needed.

[[reactive-datasource]]
==== Reactive datasource

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.jdbc.oracle.runtime.OracleAgroalConnectionConfigurer;
import io.quarkus.jdbc.oracle.runtime.OracleServiceBindingConverter;
import io.quarkus.jdbc.oracle.runtime.RollbackOnConnectionClosePoolInterceptor;

/**
* N.B. this processor is relatively simple as we rely on the /META-INF/native-image/
Expand Down Expand Up @@ -49,6 +50,12 @@ void configureAgroalConnection(BuildProducer<AdditionalBeanBuildItem> additional
.setDefaultScope(BuiltinScope.APPLICATION.getName())
.setUnremovable()
.build());

additionalBeans
.produce(new AdditionalBeanBuildItem.Builder().addBeanClass(RollbackOnConnectionClosePoolInterceptor.class)
.setDefaultScope(BuiltinScope.APPLICATION.getName())
.setUnremovable()
.build());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.quarkus.jdbc.oracle.runtime;

import java.sql.Connection;

import org.jboss.logging.Logger;

import io.agroal.api.AgroalPoolInterceptor;
import io.agroal.pool.wrapper.ConnectionWrapper;
import oracle.jdbc.OracleConnection;

/**
* Oracle has the weird behavior that it performs an implicit commit on normal connection closure (even with auto-commit
* disabled),
* which happens on application shutdown. To prevent whatever intermittent state we have during shutdown from being committed,
* we add an explicit rollback to each connection closure. If the connection has already received a COMMIT, the rollback will
* not work, which is fine.
* <p>
* The code unwraps the {@link Connection} so that we perform the rollback directly on the underlying database connection,
* and not on e.g. Agroal's {@link ConnectionWrapper} which can prevent the rollback from actually being executed due to some
* safeguards.
*
* @see https://github.com/quarkusio/quarkus/issues/36265
*/
public class RollbackOnConnectionClosePoolInterceptor implements AgroalPoolInterceptor {

private static final Logger LOG = Logger.getLogger(RollbackOnConnectionClosePoolInterceptor.class);

private final boolean noAutomaticRollback;

public RollbackOnConnectionClosePoolInterceptor() {
// if you have to use this system property, make sure you open an issue in the Quarkus tracker to explain
// why as we might need to adjust things
noAutomaticRollback = Boolean.getBoolean("quarkus-oracle-no-automatic-rollback-on-connection-close");
}

@Override
public void onConnectionDestroy(Connection connection) {
if (noAutomaticRollback) {
return;
}

// do not rollback XA connections, they are handled by the transaction manager
if (connection instanceof ConnectionWrapper connectionWrapper) {
if (connectionWrapper.getHandler().getXaResource() != null) {
return;
}
}

try {
if (connection.unwrap(Connection.class) instanceof OracleConnection oracleConnection) {
if (connection.isClosed() || connection.getAutoCommit()) {
return;
}

oracleConnection.rollback();
}
} catch (Exception e) {
LOG.trace("Ignoring exception during rollback on connection close", e);
}
}
}

0 comments on commit 757a4d4

Please sign in to comment.