ListenerList issue: need reliable notifications if adding/removing listeners from within a callback

ListenerList doc says:

If you add or remove listeners from the list during one of the callbacks - i.e. while
it's in the middle of iterating the listeners, then it's guaranteed that no listeners
will be mistakenly called after they've been removed, but it may mean that some of the
listeners could be called more than once, or not at all, depending on the list's order.

For the use case I’m working on, I need reliable callbacks for each listener, regardless of what any other listener in the system may be doing. I’d expect this guarantee from ListenerList in first place, but I can see how it hasn’t been implemented as default due to performance/memory impact. Nevertheless, I’d like to add make this option available where needed.

A simple implementation at the call site would be something like:
// auto lock (listeners.getLock());
auto listenersCopy (listeners.getListeners());
for (auto listener : listenersCopy)
{
if (listeners.contains (listener))
listener->someCallback (this);
}

  1. Is there a good way to optimise this without adding iVars to ListenerList?

  2. Instead of doing it at call site it would be better if there were variants of call()/callChecked() etc. that did the copy. However I cannot subclass ListenerList since the listeners iVar is private. Any chance of adding this to main-line JUCE?

Thanks,
Stefan

2 Likes

Would also like this.

btw, since Stefan didn’t mention it, you can see his own implementation for this here.

1 Like

By the way, that implementation of callExpectingUnregistration() is that it is subject to potential ABA problems: if during iteration a listener is removed and a new listener is added which happens to live on the same memory address as the removed one, then it’s not defined whether or not that new listener is notified in the same cycle.

I think it would be cleanest to fix all these problems by adding an iVar to ListenerList to make the remove() code aware of an ongoing iteration (optimization: only needed if there’s more than one listener).
One way could be adding an iVar pointer “currentIterator”, and set it to point to the local iterator iVar while iterating. In remove(), the code could then check and and potentially update the currentIterator.
This assumes that notification is never nested, otherwise currentIterator would need to be a list, but I think this is a very reasonable limitation.

1 Like