/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.lock;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.LockModeType;
import javax.persistence.LockTimeoutException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.PessimisticLockException;
import javax.persistence.Query;
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;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.transaction.TransactionUtil;
import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Emmanuel Bernard
*/
public class LockTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( LockTest.class );
@Test
public void testFindWithTimeoutHint() {
final Lock lock = new Lock();
lock.setName( "name" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( lock );
return lock.getId();
} );
doInJPA( this::entityManagerFactory, em -> {
Map<String, Object> properties = new HashMap<String, Object>();
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 * 1000) //5 seconds
@TestForIssue( jiraKey = "HHH-7252" )
@RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
comment = "Test verifies proper exception throwing when a lock timeout is specified.",
jiraKey = "HHH-7252" )
public void testFindWithPessimisticWriteLockTimeoutException() {
Lock lock = new Lock();
lock.setName( "name" );
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<String, Object> properties = new HashMap<String, Object>();
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
@RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class )
public void testUpdateWithPessimisticReadLockSkipLocked() {
Lock lock = new Lock();
lock.setName( "name" );
doInJPA( this::entityManagerFactory, entityManager -> {
entityManager.persist( lock );
} );
doInJPA( this::entityManagerFactory, _entityManagaer -> {
Map<String, Object> properties = new HashMap<>();
properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, LockOptions.SKIP_LOCKED );
_entityManagaer.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ, properties );
doInJPA( this::entityManagerFactory, entityManager -> {
TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) );
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() );
}
}
} );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Lock _lock = entityManager.merge( lock );
entityManager.remove( _lock );
} );
}
@Test
@RequiresDialectFeature(value = DialectChecks.SupportsLockTimeouts.class)
public void testUpdateWithPessimisticReadLockWithoutNoWait() {
Lock lock = new Lock();
lock.setName( "name" );
doInJPA( this::entityManagerFactory, entityManager -> {
entityManager.persist( lock );
} );
doInJPA( this::entityManagerFactory, _entityManager -> {
_entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_READ );
AtomicBoolean failureExpected = new AtomicBoolean();
try {
doInJPA( this::entityManagerFactory, entityManager -> {
try {
TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) );
entityManager.createNativeQuery( updateStatement() )
.setParameter( "name", "changed" )
.setParameter( "id", lock.getId() )
.executeUpdate();
}
catch (Exception e) {
if ( ExceptionUtil.isSqlLockTimeout( e ) ) {
failureExpected.set( true );
}
}
} );
}
catch (Exception e) {
if ( !failureExpected.get() ) {
fail( "Should throw LockTimeoutException or PessimisticLockException" );
}
}
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Lock _lock = entityManager.merge( lock );
entityManager.remove( _lock );
} );
}
protected String updateStatement() {
if( SQLServerDialect.class.isAssignableFrom( Dialect.getDialect().getClass() ) ) {
return "UPDATE Lock_ WITH(NOWAIT) SET name = :name where id = :id";
}
return "UPDATE Lock_ SET name = :name where id = :id";
}
@Test
public void testLockRead() throws Exception {
final Lock lock = new Lock();
lock.setName( "name" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( lock );
} );
doInJPA( this::entityManagerFactory, em -> {
Lock _lock = em.getReference( Lock.class, lock.getId() );
em.lock( _lock, LockModeType.READ );
_lock.setName( "surname" );
} );
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 {
final Lock lock = new Lock();
lock.setName( "name" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( lock );
} );
doInJPA( this::entityManagerFactory, em -> {
Lock _lock = em.getReference( Lock.class, lock.getId() );
em.lock( _lock, LockModeType.OPTIMISTIC );
_lock.setName( "surname" );
} );
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 {
final Lock lock = new Lock();
lock.setName( "second" );
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;
} );
try {
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 {
doInJPA( this::entityManagerFactory, em -> {
Lock _lock = em.getReference( Lock.class, lock.getId() );
em.remove( _lock );
} );
}
}
@Test
public void testLockWriteOnUnversioned() throws Exception {
final UnversionedLock lock = new UnversionedLock();
lock.setName( "second" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( lock );
} );
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 ) {
}
} );
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 {
final Lock lock = new Lock();
lock.setName( "force" );
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.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 {
final Lock lock = new Lock();
lock.setName( "force" );
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.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 {
final Lock lock = new Lock();
lock.setName( "force" );
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
@SkipForDialect(HSQLDialect.class)
// 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 CountDownLatch latch = new CountDownLatch( 1 );
final Lock lock = new Lock();
FutureTask<Boolean> bgTask = new FutureTask<>(
() -> {
AtomicBoolean backgroundThreadHasReadNewValue = new AtomicBoolean();
try {
doInJPA( this::entityManagerFactory, _entityManager -> {
TransactionUtil.setJdbcTimeout( _entityManager.unwrap( Session.class ) );
log.info( "testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against 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<Lock> 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 t = new Thread( bgTask );
t.setDaemon( true );
t.setName( "Lock timeout Test (bg)" );
try {
lock.setName( "testContendedPessimisticLock" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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 after being unblocked",
backgroundThreadHasReadNewValue
);
}
}
catch (InterruptedException e) {
Thread.interrupted();
}
catch (ExecutionException e) {
fail(e.getMessage());
}
} );
}
finally {
t.join(); // wait for background thread to finish before 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 {
final CountDownLatch latch = new CountDownLatch( 1 );
final Lock lock = new Lock();
FutureTask<Boolean> 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<String, Object> props = new HashMap<String, Object>();
// 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before 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 testContendedPessimisticWriteLockTimeout() throws Exception {
final CountDownLatch latch = new CountDownLatch( 1 );
final Lock lock = new Lock();
FutureTask<Boolean> 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<String, Object> props = new HashMap<String, Object>();
// 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before deleting entity
doInJPA( this::entityManagerFactory, em -> {
Lock _lock = em.getReference( Lock.class, lock.getId() );
em.remove( _lock );
} );
}
}
@Test
@RequiresDialect( { Oracle10gDialect.class, PostgreSQL81Dialect.class })
@RequiresDialectFeature( DialectChecks.SupportsLockTimeouts.class )
public void testContendedPessimisticWriteLockNoWait() throws Exception {
final CountDownLatch latch = new CountDownLatch( 1 );
final Lock lock = new Lock();
FutureTask<Boolean> 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<String, Object> props = new HashMap<String, Object>();
// 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before 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 testQueryTimeout() throws Exception {
final CountDownLatch latch = new CountDownLatch( 1 );
final Lock lock = new Lock();
FutureTask<Boolean> 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<Lock> 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before 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 testQueryTimeoutEMProps() throws Exception {
final CountDownLatch latch = new CountDownLatch( 1 );
final Map<String, Object> timeoutProps = new HashMap<String, Object>();
timeoutProps.put( QueryHints.SPEC_HINT_TIMEOUT, 500 ); // 1 sec timeout (should round up)
final Lock lock = new Lock();
FutureTask<Boolean> 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<Lock> 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before 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 testLockTimeoutEMProps() throws Exception {
final CountDownLatch latch = new CountDownLatch( 1 );
final Map<String, Object> timeoutProps = new HashMap<String, Object>();
timeoutProps.put( AvailableSettings.LOCK_TIMEOUT, 1000 ); // 1 second timeout
final Lock lock = new Lock();
FutureTask<Boolean> 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" );
doInJPA( this::entityManagerFactory, em -> {
em.persist( 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" );
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 {
t.join(); // wait for background thread to finish before deleting entity
doInJPA( this::entityManagerFactory, em -> {
Lock _lock = em.getReference( Lock.class, lock.getId() );
em.remove( _lock );
} );
}
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Lock.class,
UnversionedLock.class
};
}
}