HHH-12285 - DB connection exception on rollback causes connection leak

This commit is contained in:
Andrea Boriero 2018-02-12 11:42:47 +00:00 committed by Vlad Mihalcea
parent 745be880da
commit f972bc017f
3 changed files with 102 additions and 48 deletions

View File

@ -118,6 +118,7 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
log.trace( "Transaction rolled-back via JDBC Connection.rollback()" );
}
catch( SQLException e ) {
status = TransactionStatus.FAILED_ROLLBACK;
throw new TransactionException( "Unable to rollback against JDBC Connection", e );
}

View File

@ -36,6 +36,10 @@ public enum TransactionStatus {
* The transaction attempted to commit, but failed.
*/
FAILED_COMMIT,
/**
* The transaction attempted to rollback, but failed.
*/
FAILED_ROLLBACK,
/**
* Status code indicating a transaction that has begun the second
* phase of the two-phase commit protocol, but not yet completed

View File

@ -15,96 +15,145 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter;
import org.hibernate.jpa.test.SettingsGenerator;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
public class TransactionCommitFailureTest {
public class TransactionCommitFailureTest extends BaseUnitTestCase {
public static final String COMMIT_FAILURE = "Commit failed!";
public static final String COMMIT_FAILURE = "Error while committing the transaction";
private static final AtomicBoolean transactionFailureTrigger = new AtomicBoolean( false );
private static AtomicBoolean transactionFailureTrigger;
private static AtomicBoolean connectionIsOpen;
@Test
public void testConfiguredInterceptor() {
Map settings = basicSettings();
EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build();
EntityManager em = emf.createEntityManager();
private EntityManagerFactory emf;
try {
em.getTransaction().begin();
transactionFailureTrigger.set( true );
em.getTransaction().commit();
}
@Before
public void setUp() {
final Map settings = basicSettings();
emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build();
transactionFailureTrigger = new AtomicBoolean();
connectionIsOpen = new AtomicBoolean();
}
@After
public void tearDown() {
emf.close();
}
@Test
public void assertConnectionIsReleasedIfCommitFails() {
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
transactionFailureTrigger.set( true );
em.getTransaction().commit();
}
catch (RollbackException e) {
assertEquals( COMMIT_FAILURE, e.getCause().getMessage() );
assertEquals( COMMIT_FAILURE, e.getLocalizedMessage());
}
finally {
if ( em.getTransaction() != null && em.getTransaction().isActive() ) {
em.getTransaction().rollback();
}
em.close();
emf.close();
}
}
if ( em.getTransaction() != null && em.getTransaction().isActive() ) {
em.getTransaction().rollback();
}
em.close();
}
protected Map basicSettings() {
assertEquals( "The connection was not released", false, connectionIsOpen.get() );
}
@Test
@TestForIssue(jiraKey = "HHH-12285")
public void assertConnectionIsReleasedIfRollbackFails() {
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
assertEquals( true, connectionIsOpen.get() );
transactionFailureTrigger.set( true );
em.getTransaction().rollback();
fail( "Rollback failure, Exception expected" );
}
catch (Exception pe) {
//expected
}
finally {
em.close();
}
assertEquals( "The connection was not released", false, connectionIsOpen.get() );
}
private Map basicSettings() {
return SettingsGenerator.generateSettings(
Environment.HBM2DDL_AUTO, "create-drop",
Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true",
Environment.DIALECT, Dialect.getDialect().getClass().getName(),
Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName()
Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName()
);
}
}
public static class ProxyConnectionProvider extends DriverManagerConnectionProviderImpl {
@Override
public Connection getConnection() throws SQLException {
Connection delegate = super.getConnection();
connectionIsOpen.set( true );
return (Connection) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Connection.class},
new ConnectionInvocationHandler(delegate));
new Class[] { Connection.class },
new ConnectionInvocationHandler( delegate )
);
}
@Override
public void closeConnection(Connection conn) throws SQLException {
super.closeConnection( conn );
connectionIsOpen.set( false );
}
}
private static class ConnectionInvocationHandler implements InvocationHandler {
private static class ConnectionInvocationHandler implements InvocationHandler {
private final Connection delegate;
private final Connection delegate;
public ConnectionInvocationHandler(Connection delegate) {
this.delegate = delegate;
}
public ConnectionInvocationHandler(Connection delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("commit".equals( method.getName() )) {
if ( transactionFailureTrigger.get() ) {
throw new PersistenceException( COMMIT_FAILURE );
}
}
else if("rollback".equals( method.getName() )) {
if ( transactionFailureTrigger.get() ) {
transactionFailureTrigger.set( false );
throw new PersistenceException( "Rollback failed!" );
}
}
return method.invoke(delegate, args);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ( "commit".equals( method.getName() ) ) {
if ( transactionFailureTrigger.get() ) {
throw new SQLException( COMMIT_FAILURE );
}
}
else if ( "rollback".equals( method.getName() ) ) {
if ( transactionFailureTrigger.get() ) {
transactionFailureTrigger.set( false );
throw new SQLException( "Rollback failed!" );
}
}
return method.invoke( delegate, args );
}
}
}