============================================================================= Channel implementation notes ============================================================================= The public API of channels make them appear either opened or closed. When a channel is closed, we can't send any more items, and it will not receive any more items than already queued. Callbacks make the situation slightly more subtle. Callbacks are attached to the ChannelFactory object, so that Channel objects can be garbage-collected and still leave behind an active callback that can continue to receive items. The CHANNEL_CLOSE message is sent when a channel id is about to be removed from the ChannelFactory, which means when the Channel object has been garbage-collected *and* there is no callback any more. If a Channel object is garbage-collected but the ChannelFactory has a callback for it, a CHANNEL_LAST_MESSAGE message is sent. It is only useful if both sides' Channel objects have an associated callback. In this situation, CHANNEL_LAST_MESSAGE allows its receiver to un-register its own callback; if/when in addition the receiver side also looses the last reference to its Channel object, the Channel is closed. So in this particular situation both sides must forget about the Channel object for it to be automatically closed. gateway <---> channelfactory ---> {id: weakref(channel)} ---> {id: callback} State and invariants of Channel objects --------------------------------------- _channels and _callbacks are dictionaries on the ChannelFactory. Other attributes are on the Channel objects. All states are valid at any time (even with multithreading) unless marked with {E}, which means that they may be temporary invalid. They are eventually restored. States ("sendonly" means opened but won't receive any more items): opened sendonly closed deleted ================= ============== ================== =============== not _closed not _closed _closed not _receiveclosed _receiveclosed {E} _receiveclosed In the presence of callbacks, "deleted" does not imply "closed" nor "sendonly". It only means that no more items can be sent. The (logical) channel can continue to receive data via the call-back even if the channel object no longer exists. The two kinds of channels, with or without callback: items read by receive() has a callback ============================= ======================================= _items is a Queue _items is None id not in _callbacks state==opened: id in _callbacks {E} state==sendonly: there is {E} state!=opened: id not in _callbacks an ENDMARKER in _items {E} state==closed: there is an ENDMARKER in _items Callback calls should be considered asynchronuous. The channel can be in any state and change its state while the callback runs. The ChannelFactory's WeakValueDictionary _channels maps some ids to their channel object, depending on their state: opened sendonly closed deleted ================= ============== ================ =============== id in _channels {E} not in {E} not in not in All received RemoteErrors are handled exactly once: they are normally re-raised once in waitclose() or receive(). If it is not possible, they are at the moment dumped to stderr. (XXX should use logging/tracing) Only channels in {E} "closed" state can hold RemoteErrors. Methods: * close() returns with the channel in "closed" state * send() either send the data or raise if "closed" * receive() wait for the next item. If no item left and the state changes to non-"opened", raise * waitclose() wait for a non-"opened" state Assuming the channel is connected and the connection is alive, the local state eventually influences the state of the corresponding remote channel object: local | opened sendonly closed deleted remote | ======================================================= | opened | ok n/a (1) (2) | sendonly | n/a n/a n/a ok | closed | (1) n/a ok ok | deleted | (2) ok ok ok (1) The side with the closed channel object must send a CHANNEL_CLOSE message, which will eventually put the other side's channel in "closed" state if it is still "opened". (2) If the deleted channel has no callback, this is equivalent to (1). Otherwide, the side with the deleted channel must send a CHANNEL_LAST_MESSAGE, which will eventually put the other side's channel in "sendonly" state if it is still "opened". n/a These configuration should never occur.