HHH-6780 - Wrong Query timeout calculation

This commit is contained in:
Steve Ebersole 2011-11-28 17:39:26 -06:00
parent d00c9c85d8
commit 9a7924d9bc
8 changed files with 175 additions and 54 deletions

View File

@ -32,6 +32,7 @@ import java.sql.SQLException;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.TransactionException;
import org.hibernate.engine.jdbc.batch.spi.Batch;
import org.hibernate.engine.jdbc.batch.spi.BatchBuilder;
import org.hibernate.engine.jdbc.batch.spi.BatchKey;
@ -56,15 +57,17 @@ import org.hibernate.jdbc.WorkExecutorVisitable;
* @author Steve Ebersole
*/
public class JdbcCoordinatorImpl implements JdbcCoordinator {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, JdbcCoordinatorImpl.class.getName());
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class, JdbcCoordinatorImpl.class.getName()
);
private transient TransactionCoordinatorImpl transactionCoordinator;
private final transient LogicalConnectionImpl logicalConnection;
private transient Batch currentBatch;
private transient long transactionTimeOutInstant = -1;
public JdbcCoordinatorImpl(
Connection userSuppliedConnection,
TransactionCoordinatorImpl transactionCoordinator) {
@ -153,6 +156,14 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
return currentBatch;
}
@Override
public void executeBatch() {
if ( currentBatch != null ) {
currentBatch.execute();
currentBatch.release(); // needed?
}
}
@Override
public void abortBatch() {
if ( currentBatch != null ) {
@ -171,20 +182,26 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
}
@Override
public void setTransactionTimeOut(int timeOut) {
getStatementPreparer().setTransactionTimeOut( timeOut );
public void setTransactionTimeOut(int seconds) {
transactionTimeOutInstant = System.currentTimeMillis() + ( seconds * 1000 );
}
/**
* To be called after local transaction completion. Used to conditionally
* release the JDBC connection aggressively if the configured release mode
* indicates.
*/
@Override
public int determineRemainingTransactionTimeOutPeriod() {
if ( transactionTimeOutInstant < 0 ) {
return -1;
}
final int secondsRemaining = (int) ((transactionTimeOutInstant - System.currentTimeMillis()) / 1000);
if ( secondsRemaining <= 0 ) {
throw new TransactionException( "transaction timeout expired" );
}
return secondsRemaining;
}
@Override
public void afterTransaction() {
logicalConnection.afterTransaction();
if ( statementPreparer != null ) {
statementPreparer.unsetTransactionTimeOut();
}
transactionTimeOutInstant = -1;
}
@Override
@ -210,13 +227,6 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
}
}
public void executeBatch() {
if ( currentBatch != null ) {
currentBatch.execute();
currentBatch.release(); // needed?
}
}
@Override
public void cancelLastQuery() {
logicalConnection.getResourceRegistry().cancelLastQuery();

View File

@ -188,9 +188,7 @@ public class LogicalConnectionImpl implements LogicalConnectionImplementor {
return buildConnectionProxy();
}
/**
* {@inheritDoc}
*/
@Override
public Connection close() {
LOG.trace( "Closing logical connection" );
Connection c = isUserSuppliedConnection ? physicalConnection : null;

View File

@ -30,7 +30,6 @@ import java.sql.SQLException;
import org.hibernate.AssertionFailure;
import org.hibernate.ScrollMode;
import org.hibernate.TransactionException;
import org.hibernate.cfg.Settings;
import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
@ -41,7 +40,6 @@ import org.hibernate.engine.jdbc.spi.StatementPreparer;
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
class StatementPreparerImpl implements StatementPreparer {
private long transactionTimeOut = -1;
private JdbcCoordinatorImpl jdbcCoordinator;
StatementPreparerImpl(JdbcCoordinatorImpl jdbcCoordinator) {
@ -156,16 +154,6 @@ class StatementPreparerImpl implements StatementPreparer {
}
}
@Override
public void setTransactionTimeOut(int timeOut) {
transactionTimeOut = timeOut;
}
@Override
public void unsetTransactionTimeOut() {
transactionTimeOut = -1;
}
private abstract class StatementPreparationTemplate {
protected final String sql;
@ -191,17 +179,11 @@ class StatementPreparerImpl implements StatementPreparer {
}
private void setStatementTimeout(PreparedStatement preparedStatement) throws SQLException {
if ( transactionTimeOut > 0 ) {
int timeout = (int) ( transactionTimeOut - ( System.currentTimeMillis() / 1000 ) );
if ( timeout <= 0 ) {
throw new TransactionException( "transaction timeout expired" );
}
else {
preparedStatement.setQueryTimeout( timeout );
}
final int remainingTransactionTimeOutPeriod = jdbcCoordinator.determineRemainingTransactionTimeOutPeriod();
if ( remainingTransactionTimeOutPeriod > 0 ) {
preparedStatement.setQueryTimeout( remainingTransactionTimeOutPeriod );
}
}
}
private abstract class QueryStatementPreparationTemplate extends StatementPreparationTemplate {

View File

@ -60,6 +60,14 @@ public interface JdbcCoordinator extends Serializable {
*/
public Batch getBatch(BatchKey key);
/**
* Execute the currently managed batch (if any)
*/
public void executeBatch();
/**
* Abort the currently managed batch (if any)
*/
public void abortBatch();
/**
@ -82,16 +90,51 @@ public interface JdbcCoordinator extends Serializable {
*/
public void flushEnding();
/**
* Close this coordinator and release and resources.
*
* @return The {@link Connection} associated with the managed {@link #getLogicalConnection() logical connection}
*
* @see {@link LogicalConnection#close()}
*/
public Connection close();
/**
* Signals the end of transaction.
* <p/>
* Intended for use from the transaction coordinator, after local transaction completion. Used to conditionally
* release the JDBC connection aggressively if the configured release mode indicates.
*/
public void afterTransaction();
/**
* Perform the requested work handling exceptions, coordinating and handling return processing.
*
* @param work The work to be performed.
* @param <T> The result type.
* @return The work result.
*/
public <T> T coordinateWork(WorkExecutorVisitable<T> work);
public void executeBatch();
/**
* Attempt to cancel the last query sent to the JDBC driver.
*/
public void cancelLastQuery();
public void setTransactionTimeOut(int timeout);
/**
* Set the effective transaction timeout period for the current transaction, in seconds.
*
* @param seconds The number of seconds before a time out should occur.
*/
public void setTransactionTimeOut(int seconds);
/**
* Calculate the amount of time, in seconds, still remaining before transaction timeout occurs.
*
* @return The number of seconds remaining until until a transaction timeout occurs. A negative value indicates
* no timeout was requested.
*
* @throws org.hibernate.TransactionException Indicates the time out period has already been exceeded.
*/
public int determineRemainingTransactionTimeOutPeriod();
}

View File

@ -80,11 +80,14 @@ public interface LogicalConnection extends Serializable {
* Release the underlying connection and clean up any other resources associated
* with this logical connection.
* <p/>
* This leaves the logical connection in a "no longer useable" state.
* This leaves the logical connection in a "no longer usable" state.
*
* @return The physical connection which was being used.
* @return The application-supplied connection, or {@code null} if Hibernate was managing connection.
*/
public Connection close();
/**
* Signals the end of current transaction in which this logical connection operated.
*/
public void afterTransaction();
}

View File

@ -28,6 +28,8 @@ import java.sql.PreparedStatement;
import org.hibernate.ScrollMode;
/**
* Contracting for preparing SQL statements
*
* @author Steve Ebersole
*/
public interface StatementPreparer {
@ -91,7 +93,4 @@ public interface StatementPreparer {
* @return the prepared statement
*/
public PreparedStatement prepareQueryStatement(String sql, boolean isCallable, ScrollMode scrollMode);
public void setTransactionTimeOut(int timeout);
public void unsetTransactionTimeOut();
}

View File

@ -66,8 +66,8 @@ public class TransactionCoordinatorImpl implements TransactionCoordinator {
private final transient TransactionContext transactionContext;
private final transient JdbcCoordinatorImpl jdbcCoordinator;
private final transient TransactionFactory transactionFactory;
private final transient TransactionEnvironment transactionEnvironment;
private final transient TransactionFactory transactionFactory;
private final transient TransactionEnvironment transactionEnvironment;
private final transient List<TransactionObserver> observers;
private final transient SynchronizationRegistryImpl synchronizationRegistry;

View File

@ -0,0 +1,86 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.tm;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction;
import org.hibernate.test.jdbc.Person;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue(jiraKey = "HHH-6780")
public class TransactionTimeoutTest extends BaseCoreFunctionalTestCase {
@Override
public String[] getMappings() {
return new String[] {"jdbc/Mappings.hbm.xml"};
}
@Test
public void testJdbcCoordinatorTransactionTimeoutCheck() {
Session session = openSession();
Transaction transaction = session.getTransaction();
transaction.setTimeout( 2 );
assertEquals( -1, ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() );
transaction.begin();
assertNotSame( -1, ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() );
transaction.commit();
session.close();
}
@Test(expected = TransactionException.class)
public void testTransactionTimeoutFailure() throws InterruptedException {
Session session = openSession();
Transaction transaction = session.getTransaction();
transaction.setTimeout( 1 );
assertEquals( -1, ((SessionImplementor)session).getTransactionCoordinator().getJdbcCoordinator().determineRemainingTransactionTimeOutPeriod() );
transaction.begin();
Thread.sleep( 1000 );
session.persist( new Person( "Lukasz", "Antoniak" ) );
transaction.commit();
session.close();
}
@Test
public void testTransactionTimeoutSuccess() {
Session session = openSession();
Transaction transaction = session.getTransaction();
transaction.setTimeout( 2 );
transaction.begin();
session.persist( new Person( "Lukasz", "Antoniak" ) );
transaction.commit();
session.close();
}
}