diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/locking/LockModeTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/locking/LockModeTest.java index 36071ed830..2bf0d04ff5 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/locking/LockModeTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/locking/LockModeTest.java @@ -23,6 +23,8 @@ */ package org.hibernate.test.locking; +import java.util.concurrent.TimeoutException; + import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.PessimisticLockException; @@ -178,35 +180,139 @@ public void testQueryUsingLockOptions() { } private void nowAttemptToUpdateRow() { - Session s = sessionFactory().openSession(); - s.beginTransaction(); + // here we just need to open a new connection (database session and transaction) and make sure that + // we are not allowed to acquire exclusive locks to that row and/or write to that row. That may take + // one of two forms: + // 1) either the get-with-lock or the update fails immediately with a sql error + // 2) either the get-with-lock or the update blocks indef (in real world, it would block + // until the txn in the calling method completed. + // To be able to cater to the second type, we run this block in a separate thread to be able to "time it out" + try { - // load with write lock to deal with databases that block (wait indefinitely) direct attempts - // to write a locked row - A it = (A) s.get( - A.class, - 1, - new LockOptions( LockMode.PESSIMISTIC_WRITE ).setTimeOut( LockOptions.NO_WAIT ) + new TimedExecutor( 10*1000, 1*1000 ).execute( + new Executable() { + Session s; + + @Override + public void execute() { + s = sessionFactory().openSession(); + s.beginTransaction(); + try { + // load with write lock to deal with databases that block (wait indefinitely) direct attempts + // to write a locked row + A it = (A) s.get( + A.class, + 1, + new LockOptions( LockMode.PESSIMISTIC_WRITE ).setTimeOut( LockOptions.NO_WAIT ) + ); + it.setValue( "changed" ); + s.flush(); + fail( "Pessimistic lock not obtained/held" ); + } + catch ( Exception e ) { + // grr, exception can be any number of types based on database + // see HHH-6887 + if ( LockAcquisitionException.class.isInstance( e ) + || GenericJDBCException.class.isInstance( e ) + || PessimisticLockException.class.isInstance( e ) ) { + // "ok" + } + else { + fail( "Unexpected error type testing pessimistic locking : " + e.getClass().getName() ); + } + } + finally { + shutDown(); + } + } + + private void shutDown() { + try { + s.getTransaction().rollback(); + s.close(); + } + catch (Exception ignore) { + } + s = null; + } + + @Override + public void forceStop() { + s.cancelQuery(); + shutDown(); + } + } ); - it.setValue( "changed" ); - s.flush(); - fail( "Pessimistic lock not obtained/held" ); } - catch ( Exception e ) { - // grr, exception can be any number of types based on database - // see HHH-6887 - if ( LockAcquisitionException.class.isInstance( e ) - || GenericJDBCException.class.isInstance( e ) - || PessimisticLockException.class.isInstance( e ) ) { - // "ok" - } - else { - fail( "Unexpected error type testing pessimistic locking : " + e.getClass().getName() ); - } + catch (TimeoutException e) { + // timeout is ok, see rule #2 above } - finally { - s.getTransaction().rollback(); - s.close(); + } + + interface Executable { + public void execute(); + public void forceStop(); + } + + class TimedExecutor { + private final long timeOut; + private final int checkMilliSeconds; + + TimedExecutor(long timeOut) { + this( timeOut, 1000 ); + } + + TimedExecutor(long timeOut, int checkMilliSeconds) { + this.timeOut = timeOut; + this.checkMilliSeconds = checkMilliSeconds; + } + + public void execute(Executable executable) throws TimeoutException { + final ExecutableAdapter adapter = new ExecutableAdapter( executable ); + final Thread separateThread = new Thread( adapter ); + separateThread.start(); + + int runningTime = 0; + do { + if ( runningTime > timeOut ) { + try { + executable.forceStop(); + } + catch (Exception ignore) { + } + throw new TimeoutException(); + } + try { + Thread.sleep( checkMilliSeconds ); + runningTime += checkMilliSeconds; + } + catch (InterruptedException ignore) { + } + } while ( !adapter.isDone() ); + } + } + + class ExecutableAdapter implements Runnable { + private final Executable executable; + private boolean isDone; + + ExecutableAdapter(Executable executable) { + this.executable = executable; + } + + public boolean isDone() { + return isDone; + } + + @Override + public void run() { + isDone = false; + try { + executable.execute(); + } + finally { + isDone = true; + } } } }