-
Notifications
You must be signed in to change notification settings - Fork 45
Goex Protocol
In order for Porcelain and goon
to work together, a simple protocol is
defined that is used for all communications between the two components.
In Elixir, we always do Port.open {:spawn_executable, <path to goon>}, ...
and then exchange messages with the spawned instance of goon
. We use simple
framing to be able to pass data to the external program managed by goon
with
as little overhead as possible.
to be implemented in a future release
Before exhanging data between Elixir and goon
, we need to make sure that both
components have compatible versions. This is verified during the handshake.
The handshake is performed as follows:
-
The Goon driver spawns the
goon
executable with at least the following command-line flags:goon -proto 1.0 -ack abcd1234 <program>
The flags are defined as follows:
-
-proto
specifies the protocol version to use. Elixir andgoon
will be able to communicate only when thegoon
client supports the requested protocol version (it can support more than one). -
-ack
takes an arbitrary string as its argument. -
<program>
is the name of the program to run.
-
First thing the goon
program will output is the signature containing the ack
string and its crc32 checksum, like this:
abcd1234:1027584326:ok\0
where the first field is the ack
value, the second field is its crc32
checksum, and the third field is the handshake status. If it is not ok
, it
means an error has occurred and goon
terminates immediately.
If it is ok
, the Elixir side will check that the ack string and its checksum
are correct. After that, everything returned from goon is expected to be the
actual program output.
Because Elixir is able to communicate with goon through a single channel,
output from the external program needs to be framed in order to distinguish
stderr and stdout channels. The framing is very simple: goon
prepends one
byte to each packet it sends to Elixir. The byte is a bitmask specifying
various settings. At first we will decode only the lowest bit: 0
stands for
stdout and 1
stands for stderr.
It should be mentioned that there are actually two levels of framing going on
from the goon
's point of view. The low-level framing is implemented by
passing {:packet, 2}
option to the Erlang's open_port
function. This means
that data going in either direction between Elixir and goon
is composed of
packets prepended by 2-byte length of the packet content.
On the Elixir side packets are decoded automatically whereas on the Go side we need to do this by hand.
The application level framing is only concerned with distinguishing stdout and stderr and it has been described above.
In the future, we may decide to forward signals received by the managed OS process back to Elixir. We should be able to do that by assigning meaning to additional bits of the bitmask.
to be implemented in a future release
Data sent to goon
as input is unpacked (from the 2-byte-length Erlang
packet), decoded (see below) and then forwarded to the managed OS processes.
In order to support sending signals to external processes, we will use similar
application level framing for input packets: prepend one byte to the content.
At first only the lowest bit is examined: 0
denotes ordinary data; 1
denotes a signal number which is encoded in the next byte in the string.
Future extensions that don't change the packet format described above can be implemented by only increasing minor protocol version. Older clients should be able to work with this new version without changing their semantics.
Any future extension to the protocol that involves changing packet format will
increment the major version. Trying to spawn a goon
instance with unsupported
protocol version will fail with an error.