DefaultMergeEventListener changes CheckNullability flag - not thread save
Description
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 AMEdited
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 valuesecond threads completes
merge
and restores flag to the temporary value oftrue
Piotr FindeisenOctober 28, 2011 at 10:57 AM
Steps to reproduce should be as follow:
take an entity class
E
with non-null fieldset
checkNullability
to falseIf you now execute
merge
on new instance ofE
, you shall not receivePropertyValueException
. If you have additional validation defined for entityE
, you could receivejavax.validation.ConstraintViolationException
insteadRepeat 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 returntrue
.
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 );
}
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.