JTA CacheSynchronization can cause exceptions with stateless sessions

Description

1. Background

In a JTA environment (using an XA data source), JDBCContext registers CacheSynchronization objects for running before & after transaction completion.

If a stateless session is opened within an existing session and using the same connection, a CacheSynchronization will be registered against the StatelessSessionImpl. However, org.hibernate.transaction.CacheSynchronization.beforeCompletion() is called when the transaction ends; not when the stateless session ends.

In pseudo-code this might look like, using Spring's @Transactional aspect:

@Transactional public void test() { Session session = sessionFactory.getCurrentSession(); session.createQuery("from People").list(); StatelessSession statelessSession = sessionFactory.openStatelessSession(session.connection()); statelessSession.createQuery("from Dogs").list(); statelessSession.close(); session.createQuery("from People").list(); }

org.hibernate.impl.StatelessSessionImpl.managedFlush() will error if the session has been closed. CacheSynchronization.beforeCompletion() will call managedFlush() if:

  • StatelessSessionImpl.isFlushModeNever() is false.

  • and StatelessSessionImpl.isFlushBeforeCompletionEnabled() is true

  • and the transaction isn't rollback-only.

As expected, StatelessSessionImpl has hardcoded false and true for isFlushModeNever() and isFlushBeforeCompletionEnabled(), respectively.

The above pseudo-code will therefore fail when the transaction is committed. The top of the stack-trace is:

Exception caught from before_completion synchronization operation: org.hibernate.SessionException: Session is closed! at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:72) at org.hibernate.impl.StatelessSessionImpl.managedFlush(StatelessSessionImpl.java:332) at org.hibernate.transaction.CacheSynchronization.beforeCompletion(CacheSynchronization.java:88) at com.ibm.tx.jta.RegisteredSyncs.coreDistributeBefore(RegisteredSyncs.java:289) at com.ibm.ws.tx.jta.RegisteredSyncs.distributeBefore(RegisteredSyncs.java:150) at com.ibm.ws.tx.jta.TransactionImpl.prePrepare(TransactionImpl.java:2316) at com.ibm.ws.tx.jta.TransactionImpl.stage1CommitProcessing(TransactionImpl.java:536) at com.ibm.tx.jta.TransactionImpl.processCommit(TransactionImpl.java:983) at com.ibm.tx.jta.TransactionImpl.commit(TransactionImpl.java:918)

Looking at github, this still affects CacheSynchronization in Hibernate 3.6.

2. Proposed fix

The proposed fix is to make CacheSynchronization check TransactionFactory.Context#isClosed().

From:

flush = !ctx.isFlushModeNever() && ctx.isFlushBeforeCompletionEnabled() && !JTAHelper.isRollback( transaction.getStatus() ); //actually, this last test is probably unnecessary, since //beforeCompletion() doesn't get called during rollback

To:

flush = !ctx.isFlushModeNever() && ctx.isFlushBeforeCompletionEnabled() && !ctx.isClosed() && !JTAHelper.isRollback( transaction.getStatus() ); //actually, this last test is probably unnecessary, since //beforeCompletion() doesn't get called during rollback

2.1. Mockito-based test

/** * Check the behaviour of {@link CacheSynchronization} when the session has * been closed. * * @throws SystemException if something goes wrong */ public void testCacheSynchronisation() throws SystemException { final TransactionFactory.Context ctx = mock(TransactionFactory.Context.class); doThrow(new SessionException("Session is closed!")).when(ctx).managedFlush(); when(ctx.isClosed()).thenReturn(true); when(ctx.isFlushModeNever()).thenReturn(false); when(ctx.isFlushBeforeCompletionEnabled()).thenReturn(true); final javax.transaction.Transaction transaction = mock(javax.transaction.Transaction.class); when(transaction.getStatus()).thenReturn(0); final org.hibernate.Transaction tx = mock(org.hibernate.Transaction.class); final JDBCContext jdbcContext = mock(JDBCContext.class); new CacheSynchronization(ctx, jdbcContext, transaction, tx).beforeCompletion(); verify(jdbcContext).beforeTransactionCompletion(tx); }

2.2. Alternatives

StatelessSessionImpl.isFlushBeforeCompletionEnabled() could return a boolean based on whether or not it was closed, rather than CacheSynchronization checking for closedness itself.

Activity

Brett MeyerJuly 8, 2014 at 3:10 PM

Bulk rejecting stale issues. If this is still a legitimate issue on ORM 4, feel free to comment and attach a test case. I'll address responses case-by-case. Thanks!

Brett MeyerApril 7, 2014 at 5:44 PM

In an effort to clean up, in bulk, tickets that are most likely out of date, we're transitioning all ORM 3 tickets to an "Awaiting Test Case" state. Please see http://in.relation.to/Bloggers/HibernateORMJIRAPoliciesAndCleanUpTactics for more information.

If this is still a legitimate bug in ORM 4, please provide either a test case that reproduces it or enough detail (entities, mappings, snippets, etc.) to show that it still fails on 4. If nothing is received within 3 months or so, we'll be automatically closing them.

Thank you!

Rejected

Details

Assignee

Reporter

Components

Affects versions

Priority

Created September 9, 2011 at 4:32 PM
Updated July 8, 2014 at 3:10 PM
Resolved July 8, 2014 at 3:10 PM

Flag notifications