Skip to content
Alexei Sholik edited this page Jun 19, 2014 · 7 revisions

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.

Handshake

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:

  1. 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 and goon will be able to communicate only when the goon 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.

Communication

Output forwarding

  • implemented in protocol version 1.0

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.

Input and signal forwarding

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

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.

Clone this wiki locally