Release all ListenerHolders when clearing ListenerSet

When removing one listener from the `ListenerSet`, we release the corresponding `ListenerHolder`, which prevents the event queued or sent later than the removal from being invoked. We should also do this in the method `ListenerSet.clear()` where every listener is removed.

PiperOrigin-RevId: 652535216
This commit is contained in:
tianyifeng 2024-07-15 10:56:34 -07:00 committed by Copybara-Service
parent d747f38f59
commit ec1954c1d5
2 changed files with 52 additions and 0 deletions

View File

@ -198,6 +198,9 @@ public final class ListenerSet<T extends @NonNull Object> {
/** Removes all listeners from the set. */
public void clear() {
verifyCurrentThread();
for (ListenerHolder<T> listenerHolder : listeners) {
listenerHolder.release(iterationFinishedEvent);
}
listeners.clear();
}

View File

@ -333,6 +333,55 @@ public class ListenerSetTest {
verifyNoMoreInteractions(listener1, listener2);
}
@Test
public void clear_withRecursion_stopsReceivingEventsImmediately() {
ListenerSet<TestListener> listenerSet =
new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished);
TestListener listener2 = mock(TestListener.class);
// Listener1 clears the set from within the callback.
TestListener listener1 =
spy(
new TestListener() {
@Override
public void callback1() {
listenerSet.clear();
}
});
listenerSet.add(listener1);
listenerSet.add(listener2);
// Listener2 shouldn't even get this event as the set was cleared before the event can be
// invoked.
listenerSet.sendEvent(EVENT_ID_1, TestListener::callback1);
listenerSet.sendEvent(EVENT_ID_2, TestListener::callback2);
ShadowLooper.idleMainLooper();
verify(listener1).callback1();
// Listener1 should receive IterationFinishedEvent as the first event was invoked before the
// set was cleared.
verify(listener1).iterationFinished(createFlagSet(EVENT_ID_1));
verifyNoMoreInteractions(listener1, listener2);
}
@Test
public void clear_withQueueing_stopsReceivingEventsImmediately() {
ListenerSet<TestListener> listenerSet =
new ListenerSet<>(Looper.myLooper(), Clock.DEFAULT, TestListener::iterationFinished);
TestListener listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class);
listenerSet.add(listener1);
listenerSet.add(listener2);
// Both Listener1 and Listener2 shouldn't even get this event as they are cleared before the
// event can be invoked.
listenerSet.queueEvent(EVENT_ID_1, TestListener::callback1);
listenerSet.clear();
listenerSet.flushEvents();
ShadowLooper.idleMainLooper();
verifyNoMoreInteractions(listener1, listener2);
}
@Test
public void release_stopsForwardingEventsImmediately() {
ListenerSet<TestListener> listenerSet =