10.5 Control Messages
These are the messages that are exchanged between the ECLiPSe and remote
sides when the remote side is attached. The messages are sent on the
control connection, and are in EXDR format. All arguments for the messages
are either atoms or integers. The messages are used to
co-ordinate and synchronise the two sides. A message should only be sent from a
particular side when that side has control, and control is handed over
to the other side when the message is sent.
Most messages are used to initiate an interaction with the
other side. That is, used to cause some action to take place on the
other side: control is handed over, the action takes place on the other
side, and eventually control is handed back
when the action is completed. Control can also be explicitly handed over
from one side to the other so that the other side can initiate
interactions. Some additional messages can only be sent as part of an
interaction, for exchanges of information between the two sides. Finally,
there are messages for terminating the remote attachment. Note that
interactions can be nested, that is, an interaction from one side can
contain an interaction initiated from the other side.
The implementer for the remote interface should provide the methods for a
programmer to initiate the interactions from the remote side. These
routines would send the appropriate control messages to the ECLiPSe
side, and the messages should not be directly visible to the programmer.
The usage of each message is summarised in the diagram(s) accompanying
them. These diagrams show:
-
time proceeds downward. The ECLiPSe side is shown on the left,
the remote side on the right. Messages are shown as vertical arrows between
the two sides. The direction of the arrow indicates the direction the
message is sent.
- the context in which the message can be sent, i.e. the message from
the other side that it is either expecting as a response, or that it is a
response to. The message sequence is shown, with the message highlighted.
- any accompanying actions expected with the message. These actions are
either sending or receiving data on some other connections between the two
sides. These are shown as dashed arrows in the diagrams.
- whether nested interactions can take place between messages of an interaction.
This is indicated by vertical ellipsis between the messages in
the diagram. In such cases, the nested interaction can be initiated, and this
interaction completed before the next message for the original interaction
is expected.
From ECLiPSe side to the remote side
-
yield
- this yields control to the remote side. The message is used either to
implicitly return control to the remote side at the end of an
interaction initiated from the remote side, or it
is used to explicitly hand over control to the remote side.
The interaction-initiating messages from the remote side will be described
in more detail in their own sections.
- ec_flushio(Queue, Length)
- this message is sent when output on a remote
synchronous queue is flushed on the ECLiPSe side:
Queue is the
ECLiPSe stream number for the peer queue, and Length is the number of bytes
that is being sent on the queue. Control is yielded to the remote
side. The data on the queue Queue will be sent through the
queue after sending this message on the control connection, so on receiving
this message on the remote side, the remote side should read Length bytes from
Queue. After processing the data, the remote side should return
control to the ECLiPSe side via a resume message.
- ec_waitio(Queue)
- this message is sent when ECLiPSe requests input
from a remote synchronous queue, and the data is not available in the
queue's buffer.
This interaction is triggered when the ECLiPSe side attempts a read
operation on the empty buffer of a peer queue. The operation is suspended,
and control is yielded to remote side so that it can provide
the required data. There should be a data-provider handler associated with
the queue on the remote side. This handler should obtain the data, and send
the data to the ECLiPSe side. The data will arrive from the remote side via a
rem_flushio message, which initiates a remote flushio interaction, nested
within the ECLiPSe waitio interaction. The data arrives on the socket
associated with the remote peer queue Queue, and is automatically copied by
ECLiPSe into the peer queue buffer. Control is then yielded back to
the remote side, completing the flushio interaction. The remote side then
hands control back to ECLiPSe side by the resume message. The suspended
read operation is resumed on the now non-empty buffer.
The remote flushio interaction is described in more detail in its own
section. The main difference between a remote flushio initiated on the
remote side and one initiated by an ECLiPSe waitio described here is
that there must not be a data-consumer handler on the ECLiPSe side, as
the data is to be consumed by the suspended read operation instead.
This is ensured in the protocol by prohibiting handlers on both sides
of a synchronous peer queue.
Note that the ECLiPSe side will also listen to the control connection
while waiting for the data to be sent from the remote side. If a resume is sent before the data arrives, this is likely caused by a
programming error in the data provider handler, which finished without
sending data to the ECLiPSe side. The ECLiPSe side will print a
warning message on the warning output stream, and immediately yield
back to the remote side. Other messages are handled as normal, recursively
while waiting for the data to arrive – this is mainly intended to allow
for unexpected aborts from the remote side, although it could also be used to
perform ec_rpc calls before the remote side sends the data.
- socket_client(Port, Name, Type, Dir)
- this requests the remote side
to form a client socket connection for the remote peer queue Name. The
queue is of type Type (sync or async), and direction Dir (fromec, toec for
synchronous queues, bidirect for asynchronous queues). The client socket is
to connect at port Port with the ECLiPSe side host name.
The ECLiPSe side first creates a server socket for the peer queue Name.
The port address is Port. This, along with the details of the queue is
passed to the remote side via the socket_client message. The remote side
should then connect a client socket with Port as the port, and the Host
used for the initial attachment (which is either localhost or the hostname
of the eclipse
side) for the host.
It should also perform any additional
setups for the peer queue using the information sent with the message
(typically this involve setting up book-keeping information for the queue
on the remote side). When the remote side connection is established, it
returns control to ECLiPSe via a socket_connect message:
socket_connect(Name, Status)
Name is the name of the queue, and should be the same as the Name sent by
the socket_client message. This is used to verify that the messages refer
to same interaction. The ECLiPSe side will raise an error and disconnect
from the remote side if the name does not match. Status is either
success or fail, depending on if the remote side successfully created the
remote side of the queue or not.
If Status is success, then the ECLiPSe side will complete the connection
for the peer queue by accepting the socket connection. Since the remote end
of the socket exists, the accept operation should succeed very
quickly. If not, the operation will time-out, using the time-out interval
specified when the attachment was made. The server socket is closed
immediately after the accept operation. On successful connection,
ECLiPSe first checks that this client's host is indeed the same as the
one previously recorded for the remote side. If so, the
ECLiPSe will finish creating the ECLiPSe side of the queue. If not,
the connection is closed, and the operation is considered to have failed.
If Status from the socket_connect message is fail,
then the ECLiPSe side will clean up the preparation for the peer queue.
The ECLiPSe side then returns control to the remote side via a
socket_accept message:
socket_accept(Name,Queue)
Name is again the name of the queue, and Queue the stream id. If the accept
was unsuccessful (or if Status for socket_connect was fail), then Queue
will be the atom fail, indicating that the peer queue connection was
unsuccessful.
The remote side should then record the id Queue for later use (it is
needed for the control messages connected with this peer queue). If instead
fail was received, then the remote side should clean up the attempted queue
connection. When the remote side has finished the final stage of the
connection, control is returned to the ECLiPSe side via a resume
message, and the socket_client interaction completes.
Note that the socket_connect and socket_accept messages are
always exchanged during a socket_client interaction, even if the connection
failed on the remote side before socket_connect is sent. They also
can only occur in this context. If these messages occur in any other
occasion, an error should be raised.
- queue_close(Queue)
- this message is sent when the ECLiPSe side
closes the peer queue with id Queue.
The remote side should close the remote side of the peer queue Queue, and
remove all bookkeeping information associated with it. Control should then
be returned to ECLiPSe via a resume message. The ECLiPSe side should
also close the queue and remove bookkeeping information on the ECLiPSe side.
- disconnect
- this message is sent when ECLiPSe side initiates
disconnect.
Control is yielded to the remote side, which should acknowledge
with the disconnect_resume message. Once the ECLiPSe side receives this
message, the connection between the two sides is considered
terminated. The ECLiPSe side will then close all the connections (the
control and ec_rpc connections, and any asynchronous and synchronous
queues) to the remote side, and clean up the information associated with the
attachment. After sending the disconnect_resume message, the remote side
should also shutdown its end of the connection by closing all the
connections on its side.
Note that the disconnection message can be used to terminate the attachment
from within an interaction. In such cases, the interaction(s) would not be
completed.
- disconnect_yield
- this message is sent when ECLiPSe side receives
a disconnect message from the remote side, i.e. the remote side
initiated disconnection.
This message is sent as an acknowledgement to the
disconnect message. Once the disconnect_yield is sent, the connection is
considered terminated, and the ECLiPSe side will close all the
connections and clean up. After the clean up, abort is called. This is
done because the application on the ECLiPSe side (such as the remote
development tools) may be deep inside some interaction loop with the remote
side, and abort is the most general way of escaping from such a loop. It
can be caught (see remote_yield/1 in section 10.6) if
the user wants a more graceful termination.
Messages from remote side to ECLiPSe side
- resume
- this message hands over control from remote side to
ECLiPSe side. This is used to either implicitly return control to the
ECLiPSe side at the end of an interaction initiated from the ECLiPSe
side, or to explicitly hand over control to the remote side.
- rpc
- this message is sent before the remote side sends an ec_rpc goal
on the rpc connection.
After sending the rpc message, the remote side
should then send the ec_rpc goal (in EXDR format) on the rpc
connection. When the execution of the ec_rpc goal is finished, the
ECLiPSe side will yield control back to the remote side with a yield message, followed by the result of the ec_rpc execution on the rpc
connection (in EXDR format) – the goal with its bindings if the execution
succeeded; `fail' if the goal failed; `throw' if an exception is generated.
- rem_flushio(Queue)
- this message is sent if the remote side wish to
transfer data to the ECLiPSe side on peer queue with queue id Queue, and
the remote side does not know how many bytes will be sent with the operation.
After sending the message, control is transferred over to the ECLiPSe
side, and the data is sent on the socket stream. On the ECLiPSe side, if
the queue is a synchronous queue, then the data sent must be a single EXDR
term, because otherwise the ECLiPSe side would not know when the data
transfer is complete. The ECLiPSe side would read the data from the
socket stream as a single EXDR term, which is then written onto the
buffer. If an event handler has been associated with the peer queue, this
will now be invoked to consume the data from the buffer. If not (for
example, if the rem_flushio was initiated by an ec_waitio
message), then the data is left on the buffer to be processed later.
The rem_flushio message can also be used to sending data to ECLiPSe for
asynchronous queues as well. In this case, an event handler is directly
associated with the socket stream, and this event is invoked when the
rem_flushio message is received. The event handler goal in this case is
invoked with the `culprit' argument being the term rem_flushio(Queue, Len),
where Len is the atom `unknown'. It is up to the user-defined event handler
goal to properly read the data: since the length is unknown, the data sent
should have natural boundaries, e.g. EXDR terms, or use a mutually agreed
`end of data' marker.
- rem_flushio(Queue, Length)
- this message is sent if the remote side wish to
transfer data to the ECLiPSe side on peer queue with queue id Queue, and
the length of data to be sent is known, and is specified in Length (the
number of bytes to be sent).
After sending the message, control is transferred over to the ECLiPSe
side, and the data is sent on the socket stream. On the ECLiPSe side, if
the queue is a synchronous queue, then it would read Length bytes of data
from the socket stream and transfer the data to the queue buffer.
If an event handler has been associated with the peer queue, this
will now be invoked to consume the data from the buffer. If not (for
example, if the rem_flushio was initiated by an ec_waitio message), then
the data is left on the buffer to be processed later.
In the case that the peer queue is an asynchronous queue,
an event handler is directly
associated with the socket stream, and this event is invoked when the
rem_flushio message is received. The event handler goal in this case is
invoked with the `culprit' argument being the term rem_flushio(Queue, Length),
It is up to the user-defined event handler goal to properly read the data.
- queue_create(Name, Type, Dir, Event)
- this message is sent when the
remote side wish to initiates the creation of a new peer queue. Name is the
name of the peer queue, Type is its Type: sync for synchronous, async for
asynchronous. Dir is the data direction: fromec or toec, and Event is the
name of the event that will be raised for the event handler goal on the
ECLiPSe side, if no event is to be associated with the queue, this
should be the empty atom (
''
).
Control is handed over to ECLiPSe side, which should then set up a new
server socket for connecting the socket stream for the peer queue. Once
this server socket is set up, the creation of the queue proceeds via a
socket_client interaction from the ECLiPSe, i.e. the ECLiPSe side
sends a socket_client message. For more detail, see the description
for the socket_client message. At the end of the socket_client interaction,
the peer queue would be established, and ECLiPSe side has
control. The ECLiPSe side will yield control back to the remote side,
completing the queue_create interaction.
Note that the socket_client interaction is performed by the
ECLiPSe built-in peer_queue_create/5, this is the goal that
ECLiPSe calls on receiving the queue_create message.
If the initial creation of the socket server fails, then the ECLiPSe
side will not initiate a socket_client interaction. Instead, it will simple
yield control back to the remote side with a yield message. In this
case, no peer queue is created.
- queue_close(Queue)
- this message is sent when the remote side
closes the peer queue with id Queue.
The ECLiPSe side should close the ECLiPSe side of the peer queue Queue, and
remove all bookkeeping information associated with it. Control should then
be returned to ECLiPSe via a yield message. The queue should also be
closed on the remote side, with the bookkeeping information removed too.
- disconnect
- this message is sent if the remote side wish to initiate
disconnection.
Control is handed over to the ECLiPSe side, which will
acknowledge with disconnect_yield message. Once the ECLiPSe side
receives this message, the remote attachment between the two sides is
considered terminated. The remote side should now close all connections to the
ECLiPSe side. Concurrently, the ECLiPSe side will also close down its
end of the connections.
The disconnect message can be issued during an interaction. In such
cases, the interaction will be terminated early along with the attachment.
- disconnect_resume
- this message is sent in acknowledgement of a
disconnection initiated from the ECLiPSe side.
After sending this
message, the remote attachment between the two sides is considered
terminated. The remote side should now close all connections to the
ECLiPSe side. Concurrently, the ECLiPSe side will also close down its
end of the connections.
In addition, this message should be sent if the remote side has to
terminate the attachment while the ECLiPSe side has control. This can
happen if the remote process is forced to quit. This is the only case where
a message can be sent via the control connection on the remote side while
it does not have control. Once the message is sent, the remote side can
terminate its connection unilaterally.
10.5.1 The disconnection protocol
Under normal circumstances, the disconnection of the two sides is initiated
by the side that has control, by sending a disconnect message to the
other side. The other side acknowledges this by responding with a disconnect_yield (ECLiPSe side) or disconnect_resume (remote
side). The acknowledgement should be sent when that side is ready to
disconnect. Once the messages have been exchanged, both sides should be ready,
and can physically disconnect. The exchange of messages should ensure that
any asynchronous I/O between the two sides are properly terminated.
However, under some circumstances, a side may be forced to disconnect when
it does not have control. For example, in the Tcl remote interface, the
root window for the Tcl process may be destroyed by the user. In such
cases, a unilateral disconnect will be performed by that side – only the
second part of the normal disconnect protocol is performed by sending the
disconnect acknowledge message (disconnect_resume or disconnect_yield) without being initiated by a disconnect message.
The ECLiPSe side checks the control connection for any unexpected
incoming messages before it sends an outgoing control message. If there is
a disconnect_resume message, the ECLiPSe side will perform the
disconnection on its side.
When the user exits normally from an ECLiPSe session, ECLiPSe will
disconnect from all remote attachments. This is done in sepia_end/0
event handler.
As part of the disconnection process on the ECLiPSe side, a user
definable event will be raised in ECLiPSe, just before the remote queues
are closed. This allows the user to define application specific handlers
for dealing with the disconnection of the remote interface (on the
ECLiPSe side). A similar handler should
probably be provided on the remote side. The event raised has the same name
as the control stream. The event handler for this event is initially
defined to be true/0 (i.e. a no-op) when the remote connection is set
up. The handler can then be redefined by the user, e.g. during the
user-defined initialisation during attachment:
...
remote_connect(localhost/MyPort, Control,
set_event_handler(Control, my_disconnect_handler/1)),
...
my_disconnect_handler(Remote) :-
% just print out a message about the disconnection
printf("Disconnected from remote attachment %w", [Remote]).