HHH-13565 Ensure all events from EventListenerGroup can be fired without allocations

This commit is contained in:
Sanne Grinovero 2019-08-21 11:48:18 +01:00
parent 646a8756a9
commit 9bfffd85d7
4 changed files with 112 additions and 25 deletions

View File

@ -6,15 +6,19 @@
*/ */
package org.hibernate.event.service.internal; package org.hibernate.event.service.internal;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.event.service.spi.DuplicationStrategy; import org.hibernate.event.service.spi.DuplicationStrategy;
import org.hibernate.event.service.spi.EventActionWithParameter;
import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistrationException; import org.hibernate.event.service.spi.EventListenerRegistrationException;
import org.hibernate.event.service.spi.JpaBootstrapSensitive; import org.hibernate.event.service.spi.JpaBootstrapSensitive;
@ -23,6 +27,7 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
* @author Sanne Grinovero
*/ */
class EventListenerGroupImpl<T> implements EventListenerGroup<T> { class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
private EventType<T> eventType; private EventType<T> eventType;
@ -30,9 +35,14 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
private final Set<DuplicationStrategy> duplicationStrategies = new LinkedHashSet<>(); private final Set<DuplicationStrategy> duplicationStrategies = new LinkedHashSet<>();
// Performance: make sure a forEach iteration on this type is efficient; in particular we choose ArrayList // Performance: make sure iteration on this type is efficient; in particular we do not want to allocate iterators,
// so to avoid allocating iterators. // not having to capture state in lambdas.
private ArrayList<T> listeners; // So we keep the listeners in both a List (for convenience) and in an array (for iteration). Make sure
// their content stays in synch!
private T[] listeners = null;
//Update both fields when making changes!
private List<T> listenersAsList;
public EventListenerGroupImpl(EventType<T> eventType, EventListenerRegistryImpl listenerRegistry) { public EventListenerGroupImpl(EventType<T> eventType, EventListenerRegistryImpl listenerRegistry) {
this.eventType = eventType; this.eventType = eventType;
@ -66,7 +76,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
@Override @Override
public int count() { public int count() {
return listeners == null ? 0 : listeners.size(); final T[] ls = listeners;
return ls == null ? 0 : ls.length;
} }
@Override @Override
@ -74,16 +85,16 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
if ( duplicationStrategies != null ) { if ( duplicationStrategies != null ) {
duplicationStrategies.clear(); duplicationStrategies.clear();
} }
if ( listeners != null ) { listeners = null;
listeners.clear(); listenersAsList = null;
}
} }
@Override @Override
public final <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent) { public final <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent) {
if ( listeners != null && listeners.size() != 0 ) { final T[] ls = listeners;
if ( ls != null && ls.length != 0 ) {
final U event = eventSupplier.get(); final U event = eventSupplier.get();
for ( T listener : this.listeners ) { for ( T listener : ls ) {
actionOnEvent.accept( listener, event ); actionOnEvent.accept( listener, event );
} }
} }
@ -91,13 +102,24 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
@Override @Override
public final <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent) { public final <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent) {
if ( listeners != null ) { final T[] ls = listeners;
for ( T listener : this.listeners ) { if ( ls != null ) {
for ( T listener : ls ) {
actionOnEvent.accept( listener, event ); actionOnEvent.accept( listener, event );
} }
} }
} }
@Override
public <U,X> void fireEventOnEachListener(final U event, final X parameter, final EventActionWithParameter<T, U, X> actionOnEvent) {
final T[] ls = listeners;
if ( ls != null ) {
for ( T listener : ls ) {
actionOnEvent.applyEventToListener( listener, event, parameter );
}
}
}
@Override @Override
public void addDuplicationStrategy(DuplicationStrategy strategy) { public void addDuplicationStrategy(DuplicationStrategy strategy) {
duplicationStrategies.add( strategy ); duplicationStrategies.add( strategy );
@ -105,22 +127,44 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
/** /**
* Implementation note: should be final for performance reasons. * Implementation note: should be final for performance reasons.
* @deprecated this is not the most efficient way for iterating the event listeners.
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and co. for better alternatives.
*/ */
@Override @Override
@Deprecated
public final Iterable<T> listeners() { public final Iterable<T> listeners() {
return listeners == null ? Collections.EMPTY_LIST : listeners; final List<T> ls = listenersAsList;
return ls == null ? Collections.EMPTY_LIST : ls;
} }
@Override @Override
@SafeVarargs @SafeVarargs
public final void appendListeners(T... listeners) { public final void appendListeners(T... listeners) {
internalAppendListeners( listeners );
checkForArrayRefresh();
}
private void checkForArrayRefresh() {
final List<T> list = listenersAsList;
if ( this.listeners == null ) {
T[] a = (T[]) Array.newInstance( eventType.baseListenerInterface(), list.size() );
listeners = list.<T>toArray( a );
}
}
private void internalAppendListeners(T[] listeners) {
for ( T listener : listeners ) { for ( T listener : listeners ) {
appendListener( listener ); internalAppendListener( listener );
} }
} }
@Override @Override
public void appendListener(T listener) { public void appendListener(T listener) {
internalAppendListener( listener );
checkForArrayRefresh();
}
private void internalAppendListener(T listener) {
if ( listenerShouldGetAdded( listener ) ) { if ( listenerShouldGetAdded( listener ) ) {
internalAppend( listener ); internalAppend( listener );
} }
@ -129,28 +173,39 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
@Override @Override
@SafeVarargs @SafeVarargs
public final void prependListeners(T... listeners) { public final void prependListeners(T... listeners) {
internalPrependListeners( listeners );
checkForArrayRefresh();
}
private void internalPrependListeners(T[] listeners) {
for ( T listener : listeners ) { for ( T listener : listeners ) {
prependListener( listener ); internalPreprendListener( listener );
} }
} }
@Override @Override
public void prependListener(T listener) { public void prependListener(T listener) {
internalPreprendListener( listener );
checkForArrayRefresh();
}
private void internalPreprendListener(T listener) {
if ( listenerShouldGetAdded( listener ) ) { if ( listenerShouldGetAdded( listener ) ) {
internalPrepend( listener ); internalPrepend( listener );
} }
} }
private boolean listenerShouldGetAdded(T listener) { private boolean listenerShouldGetAdded(T listener) {
if ( listeners == null ) { final List<T> ts = listenersAsList;
listeners = new ArrayList<>(); if ( ts == null ) {
listenersAsList = new ArrayList<>();
return true; return true;
// no need to do de-dup checks // no need to do de-dup checks
} }
boolean doAdd = true; boolean doAdd = true;
strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) { strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) {
final ListIterator<T> itr = listeners.listIterator(); final ListIterator<T> itr = ts.listIterator();
while ( itr.hasNext() ) { while ( itr.hasNext() ) {
final T existingListener = itr.next(); final T existingListener = itr.next();
if ( strategy.areMatch( listener, existingListener ) ) { if ( strategy.areMatch( listener, existingListener ) ) {
@ -180,7 +235,8 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
private void internalPrepend(T listener) { private void internalPrepend(T listener) {
checkAgainstBaseInterface( listener ); checkAgainstBaseInterface( listener );
performInjections( listener ); performInjections( listener );
listeners.add( 0, listener ); listenersAsList.add( 0, listener );
listeners = null; //Marks it for refreshing
} }
private void performInjections(T listener) { private void performInjections(T listener) {
@ -206,6 +262,7 @@ class EventListenerGroupImpl<T> implements EventListenerGroup<T> {
private void internalAppend(T listener) { private void internalAppend(T listener) {
checkAgainstBaseInterface( listener ); checkAgainstBaseInterface( listener );
performInjections( listener ); performInjections( listener );
listeners.add( listener ); listenersAsList.add( listener );
listeners = null; //Marks it for refreshing
} }
} }

View File

@ -0,0 +1,17 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.event.service.spi;
import org.hibernate.Incubating;
@Incubating
@FunctionalInterface
public interface EventActionWithParameter<T, U, X> {
void applyEventToListener(T eventListener, U action, X param);
}

View File

@ -7,9 +7,11 @@
package org.hibernate.event.service.spi; package org.hibernate.event.service.spi;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.Incubating;
import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.EventType;
/** /**
@ -35,6 +37,12 @@ public interface EventListenerGroup<T> extends Serializable {
public int count(); public int count();
/**
* @deprecated this is not the most efficient way for iterating the event listeners.
* See {@link #fireEventOnEachListener(Object, BiConsumer)} and its overloaded variants for better alternatives.
* @return
*/
@Deprecated
public Iterable<T> listeners(); public Iterable<T> listeners();
/** /**
@ -67,6 +75,7 @@ public interface EventListenerGroup<T> extends Serializable {
* @param actionOnEvent * @param actionOnEvent
* @param <U> the kind of event * @param <U> the kind of event
*/ */
@Incubating
<U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent); <U> void fireLazyEventOnEachListener(final Supplier<U> eventSupplier, final BiConsumer<T,U> actionOnEvent);
/** /**
@ -76,6 +85,10 @@ public interface EventListenerGroup<T> extends Serializable {
* @param actionOnEvent * @param actionOnEvent
* @param <U> the kind of event * @param <U> the kind of event
*/ */
@Incubating
<U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent); <U> void fireEventOnEachListener(final U event, final BiConsumer<T,U> actionOnEvent);
@Incubating
<U,X> void fireEventOnEachListener(final U event, X param, final EventActionWithParameter<T,U,X> actionOnEvent);
} }

View File

@ -724,7 +724,7 @@ public final class SessionImpl
try { try {
//Uses a capturing lambda in this case as we need to carry the additional Map parameter: //Uses a capturing lambda in this case as we need to carry the additional Map parameter:
fastSessionServices.eventListenerGroup_PERSIST fastSessionServices.eventListenerGroup_PERSIST
.fireEventOnEachListener( event, (l, e) -> l.onPersist( e, copiedAlready ) ); .fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
} }
catch ( MappingException e ) { catch ( MappingException e ) {
throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ; throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ;
@ -745,7 +745,7 @@ public final class SessionImpl
checkOpenOrWaitingForAutoClose(); checkOpenOrWaitingForAutoClose();
pulseTransactionCoordinator(); pulseTransactionCoordinator();
PersistEvent event = new PersistEvent( entityName, object, this ); PersistEvent event = new PersistEvent( entityName, object, this );
fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) ); fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist );
delayedAfterCompletion(); delayedAfterCompletion();
} }
@ -793,7 +793,7 @@ public final class SessionImpl
private void fireMerge(final Map copiedAlready, final MergeEvent event) { private void fireMerge(final Map copiedAlready, final MergeEvent event) {
try { try {
pulseTransactionCoordinator(); pulseTransactionCoordinator();
fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, (l,e) -> l.onMerge( e, copiedAlready ) ); fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, copiedAlready, MergeEventListener::onMerge );
} }
catch ( ObjectDeletedException sse ) { catch ( ObjectDeletedException sse ) {
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
@ -904,7 +904,7 @@ public final class SessionImpl
private void fireDelete(final DeleteEvent event, final Set transientEntities) { private void fireDelete(final DeleteEvent event, final Set transientEntities) {
try{ try{
pulseTransactionCoordinator(); pulseTransactionCoordinator();
fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, (l,e) -> l.onDelete( e, transientEntities ) ); fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, DeleteEventListener::onDelete );
} }
catch ( ObjectDeletedException sse ) { catch ( ObjectDeletedException sse ) {
throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
@ -1179,7 +1179,7 @@ public final class SessionImpl
// it seems they prevent these hot methods from being inlined. // it seems they prevent these hot methods from being inlined.
private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) { private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) {
pulseTransactionCoordinator(); pulseTransactionCoordinator();
fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, (l,e) -> l.onLoad( e, loadType ) ); fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, loadType, LoadEventListener::onLoad );
} }
private void fireResolveNaturalId(final ResolveNaturalIdEvent event) { private void fireResolveNaturalId(final ResolveNaturalIdEvent event) {
@ -1262,7 +1262,7 @@ public final class SessionImpl
private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) { private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) {
try { try {
pulseTransactionCoordinator(); pulseTransactionCoordinator();
fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, (l,e) -> l.onRefresh( e, refreshedAlready ) ); fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, refreshedAlready, RefreshEventListener::onRefresh );
} }
catch (RuntimeException e) { catch (RuntimeException e) {
throw getExceptionConverter().convert( e ); throw getExceptionConverter().convert( e );