[hibernate-issues] [Hibernate-JIRA] Updated: (HHH-3220) Patch to prevent "org.hibernate.AssertionFailure: possible non-threadsafe access to the session" error caused by stateless sessions

Chris Wilson (JIRA) noreply at atlassian.com
Tue Feb 16 07:20:48 EST 2010


     [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-3220?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Chris Wilson updated HHH-3220:
------------------------------

    Attachment: HibernateStatelessSessionCriteriaAssertionFailureTest.java

This is a test for HHH-3220, which currently prevents us from using StatelessSession to retrieve large numbers of objects for synchronization without loading them into the Hibernate cache, which would eventually clog the cache and exhaust the available memory.

The problem is more-or-less as described in the ticket, but it's difficult to reproduce because it only occurs when the SQL generated for the (criteria or HQL) query omits a join to an associated table, because that join exceeds the maximum query depth. Therefore it normally requires a deep structure of classes to reproduce. This test forces the maximum query depth down to 1, to make it easier to reproduce.

TwoPhaseLoad.initializeEntity tries to resolve the identifier of the associated object of a ManyToOneType association:

{code}
Type[] types = persister.getPropertyTypes();
for ( int i = 0; i < hydratedState.length; i++ ) {
	final Object value = hydratedState[i];
	if ( value!=LazyPropertyInitializer.UNFETCHED_PROPERTY && value!=BackrefPropertyAccessor.UNKNOWN ) {
		hydratedState[i] = types[i].resolve( value, session, entity );
	}
}
{code}

ManyToOneType's resolve() method calls resolveIdentifier(), which calls the Session's internalLoad() method.

The identified object does not exist in the cache, because it was not loaded because its depth exceeded the maximum join depth. Therefore StatelessSessionImpl tries to load it by calling get():

{code}
EntityPersister persister = getFactory().getEntityPersister( entityName );
// first, try to load it from the temp PC associated to this SS
Object loaded = temporaryPersistenceContext.getEntity( new EntityKey( id, persister, getEntityMode() ) );
if ( loaded != null ) {
	// we found it in the temp PC.  Should indicate we are in the midst of processing a result set
	// containing eager fetches via join fetch
	return loaded;
}
if ( !eager && persister.hasProxy() ) {
	// if the metadata allowed proxy creation and caller did not request forceful eager loading,
	// generate a proxy
	return persister.createProxy( id, this );
}
// otherwise immediately materialize it
return get( entityName, id );
{code}

Which clears the entire persistence context:

{code}
public Object get(String entityName, Serializable id, LockMode lockMode) {
	errorIfClosed();
	Object result = getFactory().getEntityPersister(entityName)
			.load(id, null, lockMode, this);
	temporaryPersistenceContext.clear();
	return result;
}
{code}

Which means that the next attempt to initialize a hydrated object does not find the (parent) object in the persistence cache:

{code}
for ( int i = 0; i < hydratedObjectsSize; i++ ) {
	TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
}
{code}

which results in the assertion failure:

{code}
org.hibernate.AssertionFailure: possible non-threadsafe access to the session
	at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:123)
	at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:877)
	at org.hibernate.loader.Loader.doQuery(Loader.java:752)
	at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
	at org.hibernate.loader.Loader.doList(Loader.java:2232)
	at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2129)
	at org.hibernate.loader.Loader.list(Loader.java:2124)
	at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:118)
	at org.hibernate.impl.StatelessSessionImpl.list(StatelessSessionImpl.java:565)
	at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:306)
	at org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaAssertionFailureTest.assertList(HibernateStatelessSessionCriteriaAssertionFailureTest.java:721)
	at org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaAssertionFailureTest.testFailing(HibernateStatelessSessionCriteriaAssertionFailureTest.java:743)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.wfp.rita.test.base.HibernateTestBase.runTestMethod(HibernateTestBase.java:204)
	at org.wfp.rita.test.base.HibernateTestBase.runTest(HibernateTestBase.java:117)
	at junit.framework.TestCase.runBare(TestCase.java:130)
	at junit.framework.TestResult$1.protect(TestResult.java:106)
	at junit.framework.TestResult.runProtected(TestResult.java:124)
	at junit.framework.TestResult.run(TestResult.java:109)
	at junit.framework.TestCase.run(TestCase.java:120)
	at junit.framework.TestSuite.runTest(TestSuite.java:230)
	at junit.framework.TestSuite.run(TestSuite.java:225)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
{code}

One workaround is to increase the maximum fetch depth until the error no longer occurs, by setting the hibernate.max_fetch_depth property to a large value. However, this may have side-effects if you have badly-behaved queries. So I recommend modifying StatelessSessionImpl.internalLoad(), so that rather than call the public get() method which has the side-effect of clearing the cache, it simply retrieves the object directly without clearing the cache:

{code}
// otherwise immediately materialize it
return getFactory().getEntityPersister(entityName).load(id,
    null, LockMode.NONE, this);
{code}

Anyone who needs a solution to this problem and can't run a patched version of Hibernate can wrap the SessionWrapper class from the attached test case around their StatelessSession to easily apply this workaround.

> Patch to prevent "org.hibernate.AssertionFailure: possible non-threadsafe access to the session" error caused by stateless sessions
> -----------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HHH-3220
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3220
>             Project: Hibernate Core
>          Issue Type: Patch
>          Components: core
>    Affects Versions: 3.2.6
>         Environment: Hibernate 3.2.6, Apache Derby on Mac OSX & PC
>            Reporter: Dan Bisalputra
>            Priority: Minor
>         Attachments: HibernateStatelessSessionCriteriaAssertionFailureTest.java, StatelessSession-patch
>
>
> When performing a query in a stateless session, the query loads objects in a two-phase process in which a temporary persistence context is populated with empty objects in the first phase, then the objects' member data are read from the database in the second phase.  If one of the objects contains an association or a collection, it performs a recursive call to the session's get() method.  The get() method clears the temporary persistence context, so if the parent object contains any other associations to be read in the second phase, Hibernate throws an assertion because they are not found in the persistence context.
> This patch solves the problem by only clearing the persistence context when the recursion ends.  It passes all the unit tests for our application, but I have not tested it with any of the Hibernate unit tests.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the hibernate-issues mailing list