DefaultMergeEventListener changes CheckNullability flag - not thread save

Description

saveTransientEntity() method saves the CheckNullability setting, changes it using the getFactory().getSettings().setCheckNullability method, and does save processing. After save processing, the saved value is restored.

During load testing, we have had situations where the CheckNullability becomes set to TRUE and causes failures because of null checks.

It has been difficult to debug, but it appears that this is not thread safe - multiple threads accessing this method can change the value of this setting.

It can also be that a database error can cause an exception that will cause the checkNullability flan not to be reset.

Activity

Steve EbersoleFebruary 9, 2012 at 6:21 AM

Closing for 4.1 release

Former userJanuary 20, 2012 at 1:59 AM

Fixed by HHH-5472.

Piotr FindeisenOctober 28, 2011 at 11:02 AM
Edited

Btw. it seems very probable that the sequence the following operations done in saveTransientEntity (see source on github)

  • read global checkNullability flag

  • change global checkNullability flag to true

  • restore global checkNullability flag to read value

when executed by 2 threads at the same time can change checkNullability flag to true for ever. For this to happen it's enough that

  • first thread reads the flag and sets it temporarily to true

  • second thread reads true (the temporary value)

  • first thread completes merge and restores flag to the original value

  • second threads completes merge and restores flag to the temporary value of true

Piotr FindeisenOctober 28, 2011 at 10:57 AM

Steps to reproduce should be as follow:

  1. take an entity class E with non-null field

  2. set checkNullability to false

  3. If you now execute merge on new instance of E, you shall not receive PropertyValueException. If you have additional validation defined for entity E, you could receive javax.validation.ConstraintViolationException instead

  4. Repeat step no. 3 in two threads several times. In DefaultMergeEventListener.mergeTransientEntity PropertyValueException is caught and handled this way:

    if ( isNullabilityCheckedGlobal( source ) ) { throw ex; } else { // retry save w/o checking for non-nullable properties // (the failure will be detected later) saveTransientEntity( copy, entityName, requestedId, source, copyCache, false ); }

    Because isNullabilityCheckedGlobal is affected by by the other thread, it can, incorrectly return true.

John SMithOctober 4, 2011 at 12:59 PM

Have not managed to create a simple test case where this bug can be replicated, but with ongoing load testing, it is happening frequently. We have managed to remove all of the scenarios from our application code where null checking causes us a problem - except for one exception which occurs at approx line 104 in DefaultMergeEventListener.java

if ( isNullabilityCheckedGlobal( event.getSession() ) ) {
throw new TransientObjectException(
"one or more objects is an unsaved transient instance - save transient instance(s) before merging: " +
transientEntityNames );
}

Fixed

Details

Assignee

Reporter

Labels

Components

Fix versions

Affects versions

Priority

Created September 28, 2011 at 11:51 PM
Updated February 9, 2012 at 6:21 AM
Resolved January 20, 2012 at 1:59 AM

Flag notifications