From c15fa77f0fae300bbc944af38c74057d8600e624 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 6 Apr 2017 11:59:14 +0300 Subject: [PATCH] HHH-11616 - Refactor org.hibernate.jpa.test.lock.LockTest HHH-8001 - Apply query timeouts to Oracle follow-on locking --- .../org/hibernate/jpa/test/lock/LockTest.java | 1511 ++++++++--------- .../testing/transaction/TransactionUtil.java | 208 ++- .../hibernate/testing/util/ExceptionUtil.java | 3 +- 3 files changed, 888 insertions(+), 834 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index 1134564b79..2a350579a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -9,12 +9,11 @@ package org.hibernate.jpa.test.lock; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.LockTimeoutException; import javax.persistence.OptimisticLockException; @@ -25,6 +24,7 @@ import javax.persistence.QueryTimeoutException; import org.hibernate.LockOptions; import org.hibernate.Session; +import org.hibernate.TransactionException; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.Oracle10gDialect; @@ -36,7 +36,6 @@ import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; @@ -59,35 +58,29 @@ import static org.junit.Assert.fail; public class LockTest extends BaseEntityManagerFunctionalTestCase { private static final Logger log = Logger.getLogger( LockTest.class ); - - @Test public void testFindWithTimeoutHint() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - em.persist( lock ); - em.getTransaction().commit(); - em.close(); - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - Map properties = new HashMap(); - properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); - em.find( Lock.class, 1, LockModeType.PESSIMISTIC_WRITE, properties ); - em.getTransaction().commit(); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + return lock.getId(); + } ); - em = getOrCreateEntityManager(); - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + Map properties = new HashMap(); + properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); + em.find( Lock.class, 1, LockModeType.PESSIMISTIC_WRITE, properties ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } - @Test(timeout = 5 * 60 * 1000) //5 minutes + @Test(timeout = 5 * 1000) //5 seconds @TestForIssue( jiraKey = "HHH-7252" ) @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class, comment = "Test verifies proper exception throwing when a lock timeout is specified.", @@ -95,53 +88,40 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { public void testFindWithPessimisticWriteLockTimeoutException() { Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.close(); - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - Map properties = new HashMap(); - properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); - Lock lock2 = em2.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); - assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, em2.getLockMode( lock2 ) ); - - EntityManager em3 = createIsolatedEntityManager(); - em3.getTransaction().begin(); - try { - em3.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); - fail( "Exception should be thrown" ); - } - catch (LockTimeoutException lte) { - // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. - lte.getCause(); - } - catch (PessimisticLockException pe) { - fail( "Find with immediate timeout should have thrown LockTimeoutException." ); - } - catch (PersistenceException pe) { - log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + - "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + - "See HHH-7251 for an example of one such situation.", pe); - fail( "EntityManager should be throwing LockTimeoutException." ); - } - finally { - if (em3.getTransaction().getRollbackOnly()) { - em3.getTransaction().rollback(); - } - else { - em3.getTransaction().commit(); - } - em3.close(); - } - - em2.getTransaction().commit(); - em2.getTransaction().begin(); - em2.remove( lock2 ); - em2.getTransaction().commit(); - em2.close(); + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, _entityManager -> { + + Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE ); + assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) ); + + doInJPA( this::entityManagerFactory, entityManager -> { + try { + TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); + Map properties = new HashMap(); + properties.put( AvailableSettings.LOCK_TIMEOUT, 0L ); + + entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties ); + fail( "Exception should be thrown" ); + } + catch (LockTimeoutException lte) { + // Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set. + lte.getCause(); + } + catch (PessimisticLockException pe) { + fail( "Find with immediate timeout should have thrown LockTimeoutException." ); + } + catch (PersistenceException pe) { + log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" + + "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" + + "See HHH-7251 for an example of one such situation.", pe); + fail( "EntityManager should be throwing LockTimeoutException." ); + } + } ); + } ); } @Test @@ -154,33 +134,28 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { entityManager.persist( lock ); } ); - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - - try { + doInJPA( this::entityManagerFactory, _entityManagaer -> { Map properties = new HashMap<>(); properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, LockOptions.SKIP_LOCKED ); - em2.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ, properties ); + _entityManagaer.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ, properties ); - try { doInJPA( this::entityManagerFactory, entityManager -> { TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) ); - entityManager.createNativeQuery( updateStatement() ) + try { + entityManager.createNativeQuery( updateStatement() ) .setParameter( "name", "changed" ) .setParameter( "id", lock.getId() ) .executeUpdate(); + fail("Should throw Exception"); + } + catch (Exception e) { + if ( !ExceptionUtil.isSqlLockTimeout( e) ) { + fail( "Unknown exception thrown: " + e.getMessage() ); + } + } } ); - fail("Should throw Exception"); - } - catch (Exception e) { - if ( !ExceptionUtil.isSqlLockTimeout( e) ) { - fail( "Unknown exception thrown: " + e.getMessage() ); - } - } - } - finally { - em2.getTransaction().commit(); - } + + } ); doInJPA( this::entityManagerFactory, entityManager -> { Lock _lock = entityManager.merge( lock ); @@ -198,11 +173,8 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { entityManager.persist( lock ); } ); - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - - try { - em2.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ ); + doInJPA( this::entityManagerFactory, _entityManager -> { + _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ ); AtomicBoolean failureExpected = new AtomicBoolean(); @@ -227,10 +199,7 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { fail( "Should throw LockTimeoutException or PessimisticLockException" ); } } - } - finally { - em2.getTransaction().commit(); - } + } ); doInJPA( this::entityManagerFactory, entityManager -> { Lock _lock = entityManager.merge( lock ); @@ -247,193 +216,197 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @Test public void testLockRead() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.READ ); - lock.setName( "surname" ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - assertEquals( "surname", lock.getName() ); - em.remove( lock ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.READ ); + _lock.setName( "surname" ); + } ); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + assertEquals( "surname", _lock.getName() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimistic() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "name" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.OPTIMISTIC ); - lock.setName( "surname" ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.find( Lock.class, lock.getId() ); - assertEquals( "surname", lock.getName() ); - em.remove( lock ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.OPTIMISTIC ); + _lock.setName( "surname" ); + } ); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + assertEquals( "surname", _lock.getName() ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockWrite() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "second" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.WRITE ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.WRITE ); + return _version; + } ); + - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); try { - assertEquals( "should increase the version number EJB-106", 1, lock.getVersion() - version ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number EJB-106", 1, _lock.getVersion() - version ); + } ); } finally { - em.remove( lock ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } - em.close(); } @Test public void testLockWriteOnUnversioned() throws Exception { - UnversionedLock lock = new UnversionedLock(); + final UnversionedLock lock = new UnversionedLock(); lock.setName( "second" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - try { - // getting a READ (optimistic) lock on unversioned entity is not expected to work. - // To get the same functionality as prior release, change the LockModeType.READ lock to: - // em.lock(lock,LockModeType.PESSIMISTIC_READ); - em.lock( lock, LockModeType.READ ); - fail( "expected OptimisticLockException exception" ); - } - catch ( OptimisticLockException expected ) { - } - em.getTransaction().rollback(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - // the previous code block can be rewritten as follows (to get the previous behavior) - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_READ ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + try { + // getting a READ (optimistic) lock on unversioned entity is not expected to work. + // To get the same functionality as prior release, change the LockModeType.READ lock to: + // em.lock(lock,LockModeType.PESSIMISTIC_READ); + em.lock( _lock, LockModeType.READ ); + fail( "expected OptimisticLockException exception" ); + } + catch ( OptimisticLockException expected ) { + } + } ); - em.getTransaction().begin(); - lock = em.getReference( UnversionedLock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); + doInJPA( this::entityManagerFactory, em -> { + // the previous code block can be rewritten as follows (to get the previous behavior) + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_READ ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + UnversionedLock _lock = em.getReference( UnversionedLock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockPessimisticForceIncrement() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - try { - assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); - } - finally { - em.remove( lock ); - em.getTransaction().commit(); - } - em.close(); + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); + + return _version; + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number ", 1, _lock.getVersion() - version ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimisticForceIncrement() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - Integer version = lock.getVersion(); - em.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); - em.getTransaction().commit(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - try { - assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); - } - finally { - em.remove( lock ); - em.getTransaction().commit(); - } - em.close(); + Integer version = doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + Integer _version = _lock.getVersion(); + em.lock( _lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); + + return _version; + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + assertEquals( "should increase the version number ", 1, _lock.getVersion() - version ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test public void testLockOptimisticForceIncrementDifferentEm() throws Exception { - Lock lock = new Lock(); + final Lock lock = new Lock(); lock.setName( "force" ); - EntityManager em1 = createIsolatedEntityManager(); - em1.getTransaction().begin(); - em1.persist( lock ); - em1.getTransaction().commit(); - em1.close(); - EntityManager em2 = createIsolatedEntityManager(); - em2.getTransaction().begin(); - lock = em2.find( Lock.class, lock.getId(), LockModeType.OPTIMISTIC ); - assertEquals( "lock mode should be OPTIMISTIC ", LockModeType.OPTIMISTIC, em2.getLockMode( lock ) ); - em2.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); - assertEquals( - "lock mode should be OPTIMISTIC_FORCE_INCREMENT ", - LockModeType.OPTIMISTIC_FORCE_INCREMENT, - em2.getLockMode( lock ) - ); - em2.getTransaction().commit(); - em2.getTransaction().begin(); - em2.remove( lock ); - em2.getTransaction().commit(); - em2.close(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId(), LockModeType.OPTIMISTIC ); + assertEquals( "lock mode should be OPTIMISTIC ", LockModeType.OPTIMISTIC, em.getLockMode( _lock ) ); + em.lock( _lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); + assertEquals( + "lock mode should be OPTIMISTIC_FORCE_INCREMENT ", + LockModeType.OPTIMISTIC_FORCE_INCREMENT, + em.getLockMode( _lock ) + ); + } ); + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.find( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } @Test @@ -441,211 +414,193 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 // only. @SkipForDialect(value = { SybaseASE15Dialect.class }, strictMatching = true, jiraKey = "HHH-6820") + @SkipForDialect(value = { SQLServerDialect.class }) public void testContendedPessimisticLock() throws Exception { - final EntityManager em = getOrCreateEntityManager(); - final EntityManager isolatedEntityManager = createIsolatedEntityManager(); + final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); - Lock lock = createAndPersistLockInstance( em ); + FutureTask bgTask = new FutureTask<>( + () -> { + AtomicBoolean backgroundThreadHasReadNewValue = new AtomicBoolean(); + try { - try { - inFirstTransactionReloadAndModifyLockInstance( em, lock ); + doInJPA( this::entityManagerFactory, _entityManager -> { + TransactionUtil.setJdbcTimeout( _entityManager.unwrap( Session.class ) ); + log.info( "testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against write-locked entity" ); - final CountDownLatch latch = new CountDownLatch( 1 ); - FutureTask future = inBackgroundThreadStartSecondTransactionAndReadLockInstance( - latch, - isolatedEntityManager - ); - - // wait with timeout on the background thread - log.debug( "testContendedPessimisticLock: wait on BG thread" ); - boolean backGroundThreadCompleted = latch.await( 3, TimeUnit.SECONDS ); - - if ( backGroundThreadCompleted ) { - // the background thread read a value. At the very least we need to assert that he did not see the - // changed value - boolean backgroundThreadHasReadNewValue = future.get(); - assertFalse( - "The background thread is not allowed to see the updated value while the first transaction has not committed yet", - backgroundThreadHasReadNewValue - ); - em.getTransaction().commit(); - } - else { - log.debug( "The background thread was blocked" ); - // commit first transaction so that background thread can continue - em.getTransaction().commit(); - boolean backgroundThreadHasReadNewValue = future.get(); - assertTrue( - "Background thread should read the new value afterQuery being unblocked", - backgroundThreadHasReadNewValue - ); - } - } - finally { - cleanup( em, isolatedEntityManager, lock ); - } - } - - private void cleanup(EntityManager em, EntityManager isolatedEntityManager, Lock lock) throws InterruptedException { - // only commit the second transaction afterQuery the first one completed - isolatedEntityManager.getTransaction().commit(); - isolatedEntityManager.close(); - - // cleanup test data - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - } - - private FutureTask inBackgroundThreadStartSecondTransactionAndReadLockInstance(final CountDownLatch latch, final EntityManager isolatedEntityManager) { - FutureTask bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - isolatedEntityManager.getTransaction().begin(); - log.debug( - "testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against write-locked entity" - ); - // we should block on the following read - Query query = isolatedEntityManager.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - List resultList = query.getResultList(); - Lock lock = resultList.get( 0 ); - return lock.getName().equals( "foo" ); - } - catch ( RuntimeException e ) { - fail( "An error occurred waiting while attempting to read the entity: " + e.getMessage() ); - throw e; - } - finally { - latch.countDown(); // signal that we got the read lock + try { + // we should block on the following read + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + List resultList = query.getResultList(); + Lock _lock = resultList.get( 0 ); + backgroundThreadHasReadNewValue.set( _lock.getName().equals( "foo" ) ); + } + catch ( RuntimeException e ) { + if ( !ExceptionUtil.isSqlLockTimeout( e ) ) { + fail( "An error occurred waiting while attempting to read the entity: " + e.getMessage() ); + } + backgroundThreadHasReadNewValue.set( false ); + } + } ); + } + catch (TransactionException e) { + if( !ExceptionUtil.isConnectionClose( e ) ) { + fail("Unexpected exception: " + e.getMessage()); } } + finally { + latch.countDown(); // signal that we finished + } + return backgroundThreadHasReadNewValue.get(); } ); - Thread thread = new Thread( bgTask ); - thread.setDaemon( true ); - thread.setName( "LockTest read lock" ); - thread.start(); + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); - return bgTask; - } + try { + lock.setName( "testContendedPessimisticLock" ); - private void inFirstTransactionReloadAndModifyLockInstance(EntityManager em, Lock lock) { - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - // modify and flush, but don't commit the transaction - lock.setName( "foo" ); - em.flush(); - log.debug( "testContendedPessimisticLock: got write lock" ); - } + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - private Lock createAndPersistLockInstance(EntityManager em) { - Lock lock = new Lock(); - lock.setName( "testContendedPessimisticLock" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); - return lock; + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + // modify and flush, but don't commit the transaction + _lock.setName( "foo" ); + em.flush(); + log.info( "testContendedPessimisticLock: got write lock" ); + + try { + t.start(); + boolean backGroundThreadCompleted = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + + if ( backGroundThreadCompleted ) { + // the background thread read a value. At the very least we need to assert that he did not see the + // changed value + boolean backgroundThreadHasReadNewValue = bgTask.get(); + assertFalse( + "The background thread is not allowed to see the updated value while the first transaction has not committed yet", + backgroundThreadHasReadNewValue + ); + } + else { + log.debug( "The background thread was blocked" ); + boolean backgroundThreadHasReadNewValue = bgTask.get(); + assertTrue( + "Background thread should read the new value afterQuery being unblocked", + backgroundThreadHasReadNewValue + ); + } + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); + } + finally { + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); + } } @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticReadLockTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask = null; final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticReadLockTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticReadLockTimeout: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout is in milliseconds + props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); + try { + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_READ, props ); + } + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticReadLockTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + throw new RuntimeException( + "Expected LockTimeoutException but got unexpected exception", e + ); + } + } ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testContendedPessimisticReadLockTimeout" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticReadLockTimeout: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticReadLockTimeout: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticReadLockTimeout: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticReadLockTimeout: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout is in milliseconds - props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_READ, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticReadLockTimeout: (BG) got expected timeout exception" - ); - timedOut = true; - em2.getTransaction().rollback(); - return timedOut; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - throw new RuntimeException( - "Expected LockTimeoutException but got unexpected exception", e - ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -654,87 +609,83 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticWriteLockTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticWriteLockTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockTimeout: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout is in milliseconds + props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); + try { + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); + } + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticWriteLockTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testContendedPessimisticWriteLockTimeout" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockTimeout: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockTimeout: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticWriteLockTimeout: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockTimeout: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout is in milliseconds - props.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticWriteLockTimeout: (BG) got expected timeout exception" - ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -743,275 +694,264 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testContendedPessimisticWriteLockNoWait() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testContendedPessimisticWriteLockNoWait: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockNoWait: (BG) read write-locked entity" ); + Map props = new HashMap(); + // timeout of zero means no wait (for lock) + props.put( AvailableSettings.LOCK_TIMEOUT, 0 ); + try { + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); + } + catch ( LockTimeoutException e ) { + // success + log.info( "testContendedPessimisticWriteLockNoWait: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testContendedPessimisticWriteLockNoWait" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockNoWait: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testContendedPessimisticWriteLockNoWait: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( - "testContendedPessimisticWriteLockNoWait: (BG) about to read write-locked entity" - ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testContendedPessimisticWriteLockNoWait: (BG) read write-locked entity" ); - Map props = new HashMap(); - // timeout of zero means no wait (for lock) - props.put( AvailableSettings.LOCK_TIMEOUT, 0 ); - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props ); - } - catch ( LockTimeoutException e ) { - // success - log.info( - "testContendedPessimisticWriteLockNoWait: (BG) got expected timeout exception" - ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) - @FailureExpected( jiraKey = "HHH-8001" ) public void testQueryTimeout() throws Exception { - EntityManager em = getOrCreateEntityManager(); - final EntityManager em2 = createIsolatedEntityManager(); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testQueryTimeout: (BG) read write-locked entity" ); + try { + // we should block on the following read + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + query.setHint( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout + List resultList = query.getResultList(); + String name = resultList.get( 0 ).getName(); // force entity to be read + log.info( "testQueryTimeout: name read =" + name ); + } + catch ( QueryTimeoutException e ) { + // success + log.info( "testQueryTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + } ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testQueryTimeout" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testQueryTimeout: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testQueryTimeout: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testQueryTimeout: (BG) read write-locked entity" ); - try { - // we should block on the following read - Query query = em2.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - query.setHint( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout - List resultList = query.getResultList(); - String name = resultList.get( 0 ).getName(); // force entity to be read - log.info( "testQueryTimeout: name read =" + name ); - } - catch ( QueryTimeoutException e ) { - // success - log.info( "testQueryTimeout: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( - "testQueryTimeout: Expected LockTimeoutException but got unexpected exception", - e - ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "testQueryTimeout (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @Test @RequiresDialect( Oracle10gDialect.class ) @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) - @FailureExpected( jiraKey = "HHH-8001" ) public void testQueryTimeoutEMProps() throws Exception { - EntityManager em = getOrCreateEntityManager(); - Map queryTimeoutProps = new HashMap(); - queryTimeoutProps.put( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout (should round up) - final EntityManager em2 = createIsolatedEntityManager( queryTimeoutProps ); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); + + final Map timeoutProps = new HashMap(); + timeoutProps.put( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout (should round up) + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testQueryTimeout: (BG) read write-locked entity" ); + try { + // we should block on the following read + Query query = _entityManager.createQuery( + "select L from Lock_ L where L.id < 10000 " + ); + query.setLockMode( LockModeType.PESSIMISTIC_READ ); + List resultList = query.getResultList(); + String name = resultList.get( 0 ).getName(); // force entity to be read + log.info( "testQueryTimeout: name read =" + name ); + } + catch ( QueryTimeoutException e ) { + // success + log.info( "testQueryTimeout: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + }, timeoutProps ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testQueryTimeout" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testQueryTimeout: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testQueryTimeout: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testQueryTimeout: (BG) about to read write-locked entity" ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testQueryTimeout: (BG) read write-locked entity" ); - try { - // we should block on the following read - Query query = em2.createQuery( - "select L from Lock_ L where L.id < 10000 " - ); - query.setLockMode( LockModeType.PESSIMISTIC_READ ); - List resultList = query.getResultList(); - String name = resultList.get( 0 ).getName(); // force entity to be read - log.info( "testQueryTimeout: name read =" + name ); - } - catch ( QueryTimeoutException e ) { - // success - log.info( "testQueryTimeout: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( - "testQueryTimeout: Expected LockTimeoutException but got unexpected exception", - e - ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "testQueryTimeout (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } @@ -1020,83 +960,84 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class ) public void testLockTimeoutEMProps() throws Exception { - EntityManager em = getOrCreateEntityManager(); - Map TimeoutProps = new HashMap(); - TimeoutProps.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); // 1 second timeout - final EntityManager em2 = createIsolatedEntityManager( TimeoutProps ); - Lock lock = new Lock(); - Thread t = null; - FutureTask bgTask; final CountDownLatch latch = new CountDownLatch( 1 ); + + final Map timeoutProps = new HashMap(); + timeoutProps.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); // 1 second timeout + final Lock lock = new Lock(); + + FutureTask bgTask = new FutureTask<>( + () -> { + try { + AtomicBoolean timedOut = new AtomicBoolean(); // true (success) if LockTimeoutException occurred + + doInJPA( this::entityManagerFactory, _entityManager -> { + log.info( "testLockTimeoutEMProps: (BG) about to read write-locked entity" ); + // we should block on the following read + Lock lock2 = _entityManager.getReference( Lock.class, lock.getId() ); + lock2.getName(); // force entity to be read + log.info( "testLockTimeoutEMProps: (BG) read write-locked entity" ); + // em2 already has AvailableSettings.LOCK_TIMEOUT of 1 second applied + try { + _entityManager.lock( lock2, LockModeType.PESSIMISTIC_WRITE ); + } + catch ( LockTimeoutException e ) { + // success + log.info( "testLockTimeoutEMProps: (BG) got expected timeout exception" ); + timedOut.set( true ); + } + catch ( Throwable e ) { + log.info( "Expected LockTimeoutException but got unexpected exception", e ); + } + }, timeoutProps ); + + return timedOut.get(); + } + finally { + latch.countDown(); // signal that we finished + } + } + ); + + Thread t = new Thread( bgTask ); + t.setDaemon( true ); + t.setName( "Lock timeout Test (bg)" ); + try { lock.setName( "testLockTimeoutEMProps" ); - em.getTransaction().begin(); - em.persist( lock ); - em.getTransaction().commit(); - em.clear(); + doInJPA( this::entityManagerFactory, em -> { + em.persist( lock ); + } ); - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.lock( lock, LockModeType.PESSIMISTIC_WRITE ); - final Integer id = lock.getId(); - lock.getName(); // force entity to be read - log.info( "testLockTimeoutEMProps: got write lock" ); + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.lock( _lock, LockModeType.PESSIMISTIC_WRITE ); + final Integer id = _lock.getId(); + _lock.getName(); // force entity to be read + log.info( "testLockTimeoutEMProps: got write lock" ); - bgTask = new FutureTask( - new Callable() { - public Boolean call() { - try { - boolean timedOut = false; // true (success) if LockTimeoutException occurred - em2.getTransaction().begin(); - log.info( "testLockTimeoutEMProps: (BG) about to read write-locked entity" ); - // we should block on the following read - Lock lock2 = em2.getReference( Lock.class, id ); - lock2.getName(); // force entity to be read - log.info( "testLockTimeoutEMProps: (BG) read write-locked entity" ); - // em2 already has AvailableSettings.LOCK_TIMEOUT of 1 second applied - try { - em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE ); - } - catch ( LockTimeoutException e ) { - // success - log.info( "testLockTimeoutEMProps: (BG) got expected timeout exception" ); - timedOut = true; - } - catch ( Throwable e ) { - log.info( "Expected LockTimeoutException but got unexpected exception", e ); - } - em2.getTransaction().commit(); - return timedOut; - } - finally { - latch.countDown(); // signal that we finished - } - } - } - ); - t = new Thread( bgTask ); - t.setDaemon( true ); - t.setName( "Lock timeout Test (bg)" ); - t.start(); - boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success - assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); - assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); - em.getTransaction().commit(); + try { + t.start(); + boolean latchSet = latch.await( 10, TimeUnit.SECONDS ); // should return quickly on success + assertTrue( "background test thread finished (lock timeout is broken)", latchSet ); + assertTrue( "background test thread timed out on lock attempt", bgTask.get() ); + } + catch (InterruptedException e) { + Thread.interrupted(); + } + catch (ExecutionException e) { + fail(e.getMessage()); + } + } ); } finally { - if ( em.getTransaction().isActive() ) { - em.getTransaction().rollback(); - } - if ( t != null ) { // wait for background thread to finish beforeQuery deleting entity - t.join(); - } - em.getTransaction().begin(); - lock = em.getReference( Lock.class, lock.getId() ); - em.remove( lock ); - em.getTransaction().commit(); - em.close(); - em2.close(); + t.join(); // wait for background thread to finish beforeQuery deleting entity + + doInJPA( this::entityManagerFactory, em -> { + Lock _lock = em.getReference( Lock.class, lock.getId() ); + em.remove( _lock ); + } ); } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java index 3c86be1008..6373222f88 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java @@ -7,6 +7,8 @@ package org.hibernate.testing.transaction; import java.sql.Statement; +import java.util.Map; +import java.util.Properties; import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Function; @@ -126,18 +128,22 @@ public class TransactionUtil { * * @param factorySupplier EntityManagerFactory supplier * @param function function + * @param properties properties for entity manager bootstrapping * @param result type * * @return result */ public static T doInJPA( Supplier factorySupplier, - JPATransactionFunction function) { + JPATransactionFunction function, + Map properties) { T result = null; EntityManager entityManager = null; EntityTransaction txn = null; try { - entityManager = factorySupplier.get().createEntityManager(); + entityManager = properties == null ? + factorySupplier.get().createEntityManager(): + factorySupplier.get().createEntityManager(properties); function.beforeTransactionCompletion(); txn = entityManager.getTransaction(); txn.begin(); @@ -159,6 +165,73 @@ public class TransactionUtil { return result; } + /** + * Execute function in a JPA transaction + * + * @param factorySupplier EntityManagerFactory supplier + * @param function function + * @param result type + * + * @return result + */ + public static T doInJPA( + Supplier factorySupplier, + JPATransactionFunction function) { + return doInJPA( factorySupplier, function, null ); + } + + /** + * Execute function in a JPA transaction without return value + * + * @param factorySupplier EntityManagerFactory supplier + * @param function function + * @param properties properties for entity manager bootstrapping + */ + public static void doInJPA( + Supplier factorySupplier, + JPATransactionVoidFunction function, + Map properties) { + EntityManager entityManager = null; + EntityTransaction txn = null; + try { + entityManager = properties == null ? + factorySupplier.get().createEntityManager(): + factorySupplier.get().createEntityManager(properties); + function.beforeTransactionCompletion(); + txn = entityManager.getTransaction(); + txn.begin(); + function.accept( entityManager ); + if ( !txn.getRollbackOnly() ) { + txn.commit(); + } + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; + } + finally { + function.afterTransactionCompletion(); + if ( entityManager != null ) { + entityManager.close(); + } + } + } + /** * Execute function in a JPA transaction without return value * @@ -168,28 +241,7 @@ public class TransactionUtil { public static void doInJPA( Supplier factorySupplier, JPATransactionVoidFunction function) { - EntityManager entityManager = null; - EntityTransaction txn = null; - try { - entityManager = factorySupplier.get().createEntityManager(); - function.beforeTransactionCompletion(); - txn = entityManager.getTransaction(); - txn.begin(); - function.accept( entityManager ); - txn.commit(); - } - catch ( Throwable e ) { - if ( txn != null && txn.isActive() ) { - txn.rollback(); - } - throw e; - } - finally { - function.afterTransactionCompletion(); - if ( entityManager != null ) { - entityManager.close(); - } - } + doInJPA( factorySupplier, function, null ); } /** @@ -213,13 +265,28 @@ public class TransactionUtil { txn = session.beginTransaction(); result = function.apply( session ); - txn.commit(); - } - catch ( Throwable e ) { - if ( txn != null ) { - txn.rollback(); + if ( !txn.getRollbackOnly() ) { + txn.commit(); } - throw e; + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; } finally { function.afterTransactionCompletion(); @@ -247,13 +314,28 @@ public class TransactionUtil { txn = session.beginTransaction(); function.accept( session ); - txn.commit(); - } - catch ( Throwable e ) { - if ( txn != null ) { - txn.rollback(); + if ( !txn.getRollbackOnly() ) { + txn.commit(); } - throw e; + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; } finally { function.afterTransactionCompletion(); @@ -284,13 +366,28 @@ public class TransactionUtil { txn = session.beginTransaction(); result = function.apply( session ); - txn.commit(); - } - catch ( Throwable e ) { - if ( txn != null ) { - txn.rollback(); + if ( !txn.getRollbackOnly() ) { + txn.commit(); } - throw e; + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; } finally { function.afterTransactionCompletion(); @@ -318,13 +415,28 @@ public class TransactionUtil { txn = session.beginTransaction(); function.accept( session ); - txn.commit(); - } - catch ( Throwable e ) { - if ( txn != null ) { - txn.rollback(); + if ( !txn.getRollbackOnly() ) { + txn.commit(); } - throw e; + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; } finally { function.afterTransactionCompletion(); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java index 49caddc798..2fa3c5ba89 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java @@ -82,7 +82,8 @@ public class ExceptionUtil { public static boolean isConnectionClose(Exception e) { Throwable rootCause = ExceptionUtil.rootCause( e ); if ( rootCause != null && ( - rootCause.getMessage().toLowerCase().contains( "connection is close" ) + rootCause.getMessage().toLowerCase().contains( "connection is close" ) || + rootCause.getMessage().toLowerCase().contains( "closed connection" ) ) ) { return true; }