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.
 |