tomee-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jgallim...@apache.org
Subject [tomee] 02/06: Attempting to avoid edge case with using multiple connections from the same datasource in the same transaction
Date Thu, 04 Jul 2019 20:59:09 GMT
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 <jon@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)));
     }


Mime
View raw message