Just completed the implementations of Electrum address/scripthash/scriptpubkey/outpoint subscription/unsubscription/notification. Took me a full day to cleanly satisfy the various concurrency requirements, but once it was done it finally looked simple again.
The key insight was to (1) use an independent strand for notifications and updates to the notification map, (2) post subscript/unsubscribe from the main channel strand into the notification strand, and (3) post notification sends from the notification strand to the channel strand. This allows the notifications to be simply iterate in a for loop, posting into the notification strand buffer, and allows one request to run concurrently with the notification loop.
This not only protects the map during iteration and updates, and the socket during sends, but also keeps the socket active during the potentially long-running notification queries and loop. The queries are themselves cancellable, and this combination gives us immediate stop initiated by channel close and therefore also for node close.
Previously I had done something similar for long-running non-notification address queries, but that was initiated by the caller, on the socket, not a block or tx organized event originating from the node on an independent strand or even threadpool. So in that case it was necessary to post to an independent thread, establish a socket monitor (to capture socket cancellation), run the long cancellable query, and post the results back the channel strand, releasing the monitor.
But this doesn't satisfy the requirements of an independent job showing up at any time when the socket is either listening or even processing a request, and when the channel strand may be attempting to modify the subscription map. It was getting ugly, and this made it simple again. One strand for incoming callers, and one strand for outgoing notifications, and both bounding to/from the other like magic. :)