[Signals] Recursive callback design questions, including socket multiplexor implementation example

I have a socket multiplexor library, with a fundamental design question relating to Boost.Signals. My current implementation is flawed, and I'm considering replacing the flawed code with Boost.Signals (that is, when the performance and multi-threading issues have been addressed). Consider the following pseudo-code (ignores lots of details and is obviously not compilable - however, I do have real working code similar to this example): class Msg { ... }; class SocketStream { void sendMsg(Msg); // ... }; class Multiplexor; // forward declaration typedef boost:function<void (Msg&, Multiplexor&, SocketStream&)> IncomingMsgCallback; class Multiplexor { void registerIncomingMsgCallback (IncomingMsgCallback); void unregisterIncomingMsgCallback (IncomingMsgCallback); // ... }; A Multiplexor object has a container of callback "IncomingMsgCallback") objects which are invoked whenever an incoming message is received on a socket stream. Within the callback, a "context" is provided (reference to the enclosing Multiplexor object) for performance and ease of use. (Even if this particular callback interface did not provide a Multiplexor context, nothing would prevent the application from storing one in the callback object itself.) From within the callback invocation, additional callback objects may be registered or unregistered. (This is actually a fairly common use case - the registration of callback functionality implements application state machines, which are transitioned based on incoming messages.) The design issue is that modifying the callback container from within the callback invocation (i.e. calling "registerIncomingMsgCallback") requires some tricky guarding and managing of the internal callback container. In effect, register and unregister requests are placed on a queue, and then applied when events (messages) are not causing the callbacks to be invoked. I've found that I've duplicated this code in multiple places (primarily because there are multiple types of callbacks - e.g. incoming msg callbacks, outgoing msg callbacks, connection monitoring callbacks, periodic invocation callbacks, etc). This is Not Good, and not yet) generic. Does Boost.Signals directly address this type of use case? I've read the tutorial and examples many times, and it's still not clear to me if it does (that may be due to my understanding versus the documentation itself). Is the trackable functionality meant to address this type of use? If not, has this kind of usage been considered as relevant or desirable for Signals? I'm willing to help enhance, within the constraints of my available time. If Boost.Signals should not support this type of usage (i.e. modifying the signal, indirectly, from within a connected slot), is it worthwhile to create a generic container to support this functionality? Or are my assumptions and approaches way off base, and I should be considering other alternatives? (I haven't described every possibility that I considered during design ...) Thoughts and comments are greatly appreciated! Cliff

On May 13, 2005, at 4:05 PM, Cliff Green wrote:
The design issue is that modifying the callback container from within the callback invocation (i.e. calling "registerIncomingMsgCallback") requires some tricky guarding and managing of the internal callback container. In effect, register and unregister requests are placed on a queue, and then applied when events (messages) are not causing the callbacks to be invoked.
I've found that I've duplicated this code in multiple places (primarily because there are multiple types of callbacks - e.g. incoming msg callbacks, outgoing msg callbacks, connection monitoring callbacks, periodic invocation callbacks, etc). This is Not Good, and not yet) generic.
Does Boost.Signals directly address this type of use case? I've read the tutorial and examples many times, and it's still not clear to me if it does (that may be due to my understanding versus the documentation itself). Is the trackable functionality meant to address this type of use? If not, has this kind of usage been considered as relevant or desirable for Signals? I'm willing to help enhance, within the constraints of my available time.
Wow, you posted this a long time ago; my apologies for the delay, I'd marked it as important and then forgotten it completely. Signals is designed to handle this use case and you're right: it's tricky. You can have a signal call a slot that calls the same signal that calls some slots that add/remove slots from the signal, and that's perfectly fine. We don't queue requests (because the request could come from a slot that's been deleted, so we absolutely must not call it), but rather mark slots as "removed" and do cleanups when there are no more outstanding signal invocations. Take a look at the "random_signal_system.cpp" torture test in libs/signals/test, where it does lots of random, recursive signal invocations that disconnect random slots. This actually doesn't have much to do with "trackable" in Signals; trackable just handles automatically disconnecting slots when one of the objects involved in the slot dies. Doug
participants (2)
-
Cliff Green
-
Doug Gregor