HHH-16047 Allow reusing of FlushEntityEvent instances

This commit is contained in:
Sanne Grinovero 2023-01-15 23:19:50 +00:00 committed by Sanne Grinovero
parent 9f88b56099
commit c570b11dcd
2 changed files with 77 additions and 4 deletions

View File

@ -203,15 +203,24 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi
final Map.Entry<Object,EntityEntry>[] entityEntries = persistenceContext.reentrantSafeEntityEntries(); final Map.Entry<Object,EntityEntry>[] entityEntries = persistenceContext.reentrantSafeEntityEntries();
final int count = entityEntries.length; final int count = entityEntries.length;
FlushEntityEvent entityEvent = null; //allow reuse of the event as it's heavily allocated in certain use cases
int eventGenerationId = 0; //Used to double-check the instance reuse won't cause problems
for ( Map.Entry<Object,EntityEntry> me : entityEntries ) { for ( Map.Entry<Object,EntityEntry> me : entityEntries ) {
// Update the status of the object and if necessary, schedule an update // Update the status of the object and if necessary, schedule an update
EntityEntry entry = me.getValue(); EntityEntry entry = me.getValue();
Status status = entry.getStatus(); Status status = entry.getStatus();
if ( status != Status.LOADING && status != Status.GONE ) { if ( status != Status.LOADING && status != Status.GONE ) {
final FlushEntityEvent entityEvent = new FlushEntityEvent( source, me.getKey(), entry ); entityEvent = createOrReuseEventInstance( entityEvent, source, me.getKey(), entry );
entityEvent.setInstanceGenerationId( ++eventGenerationId );
flushListeners.fireEventOnEachListener( entityEvent, FlushEntityEventListener::onFlushEntity ); flushListeners.fireEventOnEachListener( entityEvent, FlushEntityEventListener::onFlushEntity );
entityEvent.setAllowedToReuse( true );
assert entityEvent.getInstanceGenerationId() == eventGenerationId;
} }
} }
@ -220,6 +229,26 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi
return count; return count;
} }
/**
* Reuses a FlushEntityEvent for a new purpose, if possible;
* if not possible a new actual instance is returned.
*/
private FlushEntityEvent createOrReuseEventInstance(
FlushEntityEvent possiblyValidExistingInstance,
EventSource source,
Object key,
EntityEntry entry) {
FlushEntityEvent entityEvent = possiblyValidExistingInstance;
if ( entityEvent == null || (! entityEvent.isAllowedToReuse() ) ) {
//need to create a new instance
entityEvent = new FlushEntityEvent( source, key, entry );
}
else {
entityEvent.resetAndReuseEventInstance( key, entry );
}
return entityEvent;
}
/** /**
* process any unreferenced collections and then inspect all known collections, * process any unreferenced collections and then inspect all known collections,
* scheduling creates/removes/updates * scheduling creates/removes/updates

View File

@ -12,15 +12,18 @@ import org.hibernate.engine.spi.EntityEntry;
* @author Gavin King * @author Gavin King
*/ */
public class FlushEntityEvent extends AbstractEvent { public class FlushEntityEvent extends AbstractEvent {
private final Object entity;
private Object entity;
private Object[] propertyValues; private Object[] propertyValues;
private Object[] databaseSnapshot; private Object[] databaseSnapshot;
private int[] dirtyProperties; private int[] dirtyProperties;
private boolean hasDirtyCollection; private boolean hasDirtyCollection;
private boolean dirtyCheckPossible; private boolean dirtyCheckPossible;
private boolean dirtyCheckHandledByInterceptor; private boolean dirtyCheckHandledByInterceptor;
private final EntityEntry entityEntry; private EntityEntry entityEntry;
private boolean allowedToReuse;//allows this event instance to be reused for multiple events: special case to GC
private int instanceGenerationId;//in support of event instance reuse: to double check no recursive/nested use is happening
public FlushEntityEvent(EventSource source, Object entity, EntityEntry entry) { public FlushEntityEvent(EventSource source, Object entity, EntityEntry entry) {
super(source); super(source);
this.entity = entity; this.entity = entity;
@ -75,4 +78,45 @@ public class FlushEntityEvent extends AbstractEvent {
public Object getEntity() { public Object getEntity() {
return entity; return entity;
} }
/**
* This is a terrible anti-pattern, but particular circumstances call for being
* able to reuse the same event instance: this is otherwise allocated in hot loops
* and since each event is escaping the scope it's actually causing allocation issues.
* The flush event does not appear to be used recursively so this is currently safe to
* do, nevertheless we add an allowedToReuse flag to ensure only instances whose
* purpose has completed are being reused.
* N.B. two out of three parameters from the constructor are reset: the same EventSource is implied
* on reuse.
* @param entity same as constructor parameter
* @param entry same as constructor parameter
*/
public void resetAndReuseEventInstance(Object entity, EntityEntry entry) {
this.entity = entity;
this.entityEntry = entry;
this.allowedToReuse = false;
//and reset other fields to the default:
this.propertyValues = null;
this.databaseSnapshot = null;
this.dirtyProperties = null;
this.hasDirtyCollection = false;
this.dirtyCheckPossible = false;
this.dirtyCheckHandledByInterceptor = false;
}
public boolean isAllowedToReuse() {
return this.allowedToReuse;
}
public void setAllowedToReuse(final boolean allowedToReuse) {
this.allowedToReuse = allowedToReuse;
}
public int getInstanceGenerationId() {
return this.instanceGenerationId;
}
public void setInstanceGenerationId(final int instanceGenerationId) {
this.instanceGenerationId = instanceGenerationId;
}
} }