You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jg...@apache.org on 2019/07/04 20:59:09 UTC
[tomee] 02/06: Attempting to avoid edge case with using multiple
connections from the same datasource in the same transaction
This is an automated email from the ASF dual-hosted git repository.
jgallimore pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee.git
commit 64d3ce583ce92c0d33975fcedbd0d4e079b63798
Author: Jonathan Gallimore <jo...@jrg.me.uk>
AuthorDate: Thu Jul 4 15:21:17 2019 +0100
Attempting to avoid edge case with using multiple connections from the
same datasource in the same transaction
---
.../openejb/resource/jdbc/managed/local/Key.java | 81 ++++++++++++++++++++++
.../jdbc/managed/local/ManagedConnection.java | 63 +++++------------
.../jdbc/managed/local/ManagedDataSource.java | 34 +++++++--
.../jdbc/managed/xa/ManagedXADataSource.java | 12 ++++
4 files changed, 142 insertions(+), 48 deletions(-)
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/Key.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/Key.java
new file mode 100644
index 0000000..87351c7
--- /dev/null
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/Key.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.openejb.resource.jdbc.managed.local;
+
+import javax.sql.CommonDataSource;
+import java.util.Objects;
+
+public class Key {
+ private final CommonDataSource ds;
+ private final String user;
+ private final String pwd;
+ private final int hash;
+
+ public Key(final CommonDataSource ds, final String user, final String pwd) {
+ this.ds = ds;
+ this.user = user;
+ this.pwd = pwd;
+
+ int result = ds.hashCode();
+ result = 31 * result + (user != null ? user.hashCode() : 0);
+ result = 31 * result + (pwd != null ? pwd.hashCode() : 0);
+ hash = result;
+ }
+
+ public CommonDataSource getDs() {
+ return ds;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getPwd() {
+ return pwd;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Key key = Key.class.cast(o);
+ return (ds == key.ds || ds.equals(key.ds)) &&
+ Objects.equals(user, key.user) &&
+ Objects.equals(pwd, key.pwd);
+ }
+
+ @Override
+ public String toString() {
+ return "Key{" +
+ "ds=" + ds +
+ ", user='" + user + '\'' +
+ ", pwd='*****'" +
+ ", hash=" + hash +
+ '}';
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+}
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedConnection.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedConnection.java
index 9445a30..95cf1fa 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedConnection.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedConnection.java
@@ -40,7 +40,6 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
import java.util.Arrays;
-import java.util.Objects;
public class ManagedConnection implements InvocationHandler {
@@ -152,7 +151,7 @@ public class ManagedConnection implements InvocationHandler {
throw new SQLException("Unable to enlist connection the transaction", e);
}
- registry.putResource(key, delegate);
+ registry.putResource(key, proxy);
transaction.registerSynchronization(new ClosingSynchronization());
if (xaConnection == null) {
@@ -168,8 +167,19 @@ public class ManagedConnection implements InvocationHandler {
}
}
}
- } else if (delegate == null) { // shouldn't happen
- delegate = connection;
+ } else if (delegate == null) {
+ // this happens if the caller obtains subsequent connections from the *same* datasource
+ // are enlisted in the *same* transaction:
+ // connection != null (because it comes from the tx registry)
+ // delegate == null (because its a new ManagedConnection instance)
+ // we attempt to work-around this by looking up the connection in the tx registry in ManaagedDataSource
+ // and ManagedXADataSource, but there is an edge case where the connection is fetch from the datasource
+ // first, and a BMT tx is started by the user.
+
+ final ManagedConnection managedConnection = ManagedConnection.class.cast(Proxy.getInvocationHandler(connection));
+ this.delegate = managedConnection.delegate;
+ this.xaConnection = managedConnection.xaConnection;
+ this.xaResource = managedConnection.xaResource;
}
return invokeUnderTransaction(method, args);
@@ -204,9 +214,10 @@ public class ManagedConnection implements InvocationHandler {
}
protected Object newConnection() throws SQLException {
- final Object connection = DataSource.class.isInstance(key.ds) ?
- (key.user == null ? DataSource.class.cast(key.ds).getConnection() : DataSource.class.cast(key.ds).getConnection(key.user, key.pwd)) :
- (key.user == null ? XADataSource.class.cast(key.ds).getXAConnection() : XADataSource.class.cast(key.ds).getXAConnection(key.user, key.pwd));
+ final Object connection = DataSource.class.isInstance(key.getDs()) ?
+ (key.getUser() == null ? DataSource.class.cast(key.getDs()).getConnection() : DataSource.class.cast(key.getDs()).getConnection(key.getUser(), key.getPwd())) :
+ (key.getUser() == null ? XADataSource.class.cast(key.getDs()).getXAConnection() : XADataSource.class.cast(key.getDs()).getXAConnection(key.getUser(), key.getPwd()));
+
if (XAConnection.class.isInstance(connection)) {
xaConnection = XAConnection.class.cast(connection);
xaResource = xaConnection.getXAResource();
@@ -273,7 +284,7 @@ public class ManagedConnection implements InvocationHandler {
return null;
}
- private static boolean isUnderTransaction(final int status) {
+ public static boolean isUnderTransaction(final int status) {
return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
}
@@ -341,41 +352,5 @@ public class ManagedConnection implements InvocationHandler {
'}';
}
- private static final class Key {
- private final CommonDataSource ds;
- private final String user;
- private final String pwd;
- private final int hash;
-
- private Key(final CommonDataSource ds, final String user, final String pwd) {
- this.ds = ds;
- this.user = user;
- this.pwd = pwd;
-
- int result = ds.hashCode();
- result = 31 * result + (user != null ? user.hashCode() : 0);
- result = 31 * result + (pwd != null ? pwd.hashCode() : 0);
- hash = result;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- Key key = Key.class.cast(o);
- return (ds == key.ds || ds.equals(key.ds)) &&
- Objects.equals(user, key.user) &&
- Objects.equals(pwd, key.pwd);
- }
- @Override
- public int hashCode() {
- return hash;
- }
- }
}
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedDataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedDataSource.java
index 5335fef..508aae8 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedDataSource.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/local/ManagedDataSource.java
@@ -17,6 +17,14 @@
package org.apache.openejb.resource.jdbc.managed.local;
+import org.apache.openejb.util.LogCategory;
+
+import javax.sql.CommonDataSource;
+import javax.sql.DataSource;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
import java.io.ObjectStreamException;
import java.io.PrintWriter;
import java.io.Serializable;
@@ -25,12 +33,9 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
-import javax.sql.CommonDataSource;
-import javax.sql.DataSource;
-import javax.transaction.TransactionManager;
-import javax.transaction.TransactionSynchronizationRegistry;
public class ManagedDataSource implements DataSource, Serializable {
+ private static final org.apache.openejb.util.Logger LOGGER = org.apache.openejb.util.Logger.getInstance(LogCategory.OPENEJB_RESOURCE_JDBC, ManagedDataSource.class);
private static final Class<?>[] CONNECTION_CLASS = new Class<?>[]{Connection.class};
protected final CommonDataSource delegate;
@@ -95,10 +100,31 @@ public class ManagedDataSource implements DataSource, Serializable {
}
private Connection managed(final String u, final String p) {
+ final Connection resource = getTxConnection(delegate, u, p, transactionManager, registry);
+ if (resource != null) {
+ return resource;
+ }
+
return (Connection) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), CONNECTION_CLASS,
new ManagedConnection(delegate, transactionManager, registry, u, p));
}
+ protected static Connection getTxConnection(final CommonDataSource delegate, final String u, final String p, final TransactionManager transactionManager, final TransactionSynchronizationRegistry registry) {
+ try {
+ final Transaction transaction = transactionManager.getTransaction();
+ if (transaction != null && ManagedConnection.isUnderTransaction(transaction.getStatus())) {
+ final Object resource = registry.getResource(new Key(delegate, u, p));
+ if (Connection.class.isInstance(resource)) {
+ return Connection.class.cast(resource);
+ }
+ }
+ } catch (SystemException e) {
+ // we wouldn't expect this to happen, but lets log it and fall through to the previous behaviour
+ LOGGER.warning("Attempting to get the current transaction failed with an error: " + e.getMessage(), e);
+ }
+ return null;
+ }
+
public CommonDataSource getDelegate() {
return delegate;
}
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/xa/ManagedXADataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/xa/ManagedXADataSource.java
index c2ccced..341cf23 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/xa/ManagedXADataSource.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/managed/xa/ManagedXADataSource.java
@@ -17,16 +17,24 @@
package org.apache.openejb.resource.jdbc.managed.xa;
+import org.apache.openejb.resource.jdbc.managed.local.Key;
+import org.apache.openejb.resource.jdbc.managed.local.ManagedConnection;
import org.apache.openejb.resource.jdbc.managed.local.ManagedDataSource;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.CommonDataSource;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
public class ManagedXADataSource extends ManagedDataSource {
+
+ private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_RESOURCE_JDBC, ManagedXADataSource.class);
private static final Class<?>[] CONNECTION_CLASS = new Class<?>[]{Connection.class};
private final TransactionManager txMgr;
@@ -47,6 +55,10 @@ public class ManagedXADataSource extends ManagedDataSource {
}
private Connection managedXA(final String u, final String p) throws SQLException {
+ final Connection resource = getTxConnection(delegate, u, p, transactionManager, registry);
+ if (resource != null) {
+ return resource;
+ }
return Connection.class.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), CONNECTION_CLASS,
new ManagedXAConnection(delegate, txMgr, registry, u, p)));
}