123 lines
4.9 KiB
Plaintext
123 lines
4.9 KiB
Plaintext
|
=============================================================================
|
||
|
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 <no ref left>
|
||
|
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.
|