smarter pub-sub A better event dispatcher
Photo by Fotis Fotopouloson unsplash.com
Usual Implementation problem
- Typical Event subscriptions where observers (or listeners) register with a subject (or publisher) to receive events have a usual issue in most implementations which is commonly known as the Lapsed listener problem
- This requires explicit deregistration, as in the typical dispose pattern paradigm.
- The leak happens when an observer fails to unsubscribe from the subject when it no longer needs to listen. Consequently, the subject still holds a strong reference to the observer which prevents it from being garbage collected.
The Usual Resolution Mechanism
- This can be prevented by the subject holding weak references to the observers, allowing them to be garbage collected as normal without needing to be unregistered.
- Thus the idea is to keep only the std::weak_ptr in event dispatcher and do the (lazy) cleanup on event invocation.
- Probably something along the lines of -
Approxmiate event dispatcher: common resolution mechanism
- And a simple notifier would look something like this
Sample Notifier for the event dispatcher
OR we can opt for the classic “erase and update” using iterator based loop and merge these two loop operation(s).
There is an issue with this approach, what’s that?
What seems to be the problem?
The Trouble in Paradise
What if the observer tries to update you while being notified ???Suppose something in an observer you fire, ends up in -
- Adding or removing observers? => Bad (includes crashes!)
- Blocking the action of another thread, who blocks on trying to add an observer? => Bad (deadlocks!)
Basically the problem is with Reentrancy here.
- Never, ever leave control of your code while holding the lock.
- Holding a lock and calling a callback is a no-no.
Tying the loose ends (Simple fix)
“Copy and Broadcast” — While copying, remove expired observers (from original and hence excluded from the copy), then fire off observers from the copied list.
After this fix, a notifier would roughly look something like -
Copy And Broadcast mechanism
Tying the loose ends (Better but complex fix)
- Maintaining a non-blocking lock-free task queue that includes add/remove/notify etc. events.
- In case the event handlers take good amount of time, it might be considered launching them “all at once”, i.e. in their own threads over which join takes place, or maybe using executor mechanism etc.