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

The actual work of launching external processes and communicating with them is performed by the so called drivers: modules implementing a specified set of functions. Functions in the Porcelain module delegate work to the currently selected driver's implementations.

The basic functionality that provides a saner and more convenient API on top of ports is implemented in the Porcelain.Driver.Basic (or just Basic) driver.

Additional functionality that includes circumventing the "eof bug" and sending signals to OS processes will be implemented in the Porcelain.Driver.Goon (or just Goon) driver.

Common functionality

The following tasks are going to be performed by all drivers, so they should be implemented in a shared module.

  • splitting the shell-like invocation into program name and a list of arguments (Porcelain.shplit) (see this and this for reference)

  • processing and validating given options (currently partly done in the Porcelain module)

  • defining a set of Erlang port options shared accross all drivers [done]

The receive loop

The receive loop collects messages from the port and dispatches them to the caller according to the chosen output channel.

To implement the async API, drivers spawn additional processes as needed. The output received from the port connected to an external process is fed immediately to the target (where the target can be a file path, a file process, or a stream) or kept in memory until requested or discarded.

The Basic driver

This section describes the details of the pure Elixir implementation of the Porcelain API.

It serves as a light shim on top of ports. The main responsibilities of the driver are:

  • building the invocation of Port.open
  • implementing a data handler to collect output from the program

The port is opened in stream mode to communicate directly with the external program.

The Goon driver

This section describes the details of interacting with the goon middleman from Elixir. See below for the description of goon's implementation.

The main responsibilities of the driver are:

  • building the invocation of Port.open

  • performing the handshake with goon to select the protocol for communications and perform any other required configuration (will most likely be implemented as a set of command-line flags and arguments to the goon program) [to be implemented in a future release]

  • implementing a data handler to collect output from goon

  • sending signals to the managed OS process [to be implemented in a future release]

The port is opened in {:packet, 2} mode. Then, the handshake is performed. In case of failure, an error tuple is returned to the client. If the handshake succeeded, the driver enters its receive loop and starts dispatching output to the caller.

Sending signals is implemented by means of sending messages to the driver which will then forward them to the port program.

The middleman

Implemented in Go, goon provides a lightweight solution for patching some holes in the Erlang port functionality, namely:

  • being able to interact with "eof-driven" programs
  • being able to send signals to OS processes

It will be detected at run time by default, but the user should be able to override the setting: to either never use goon or to require goon to be present in PATH.

In terms of OS processes, the hierarchy when using the Goon driver will look as follows:

    +-----------+
    | Erlang VM |
    +-----------+
         ||
         \/
      +------+
      | Goon |
      +------+
         ||
         \/
+------------------+
| External program |
+------------------+

Arrows pointing down indicate the parent→child relationship between the OS processes.

The Go implementation will use packages from Go's stdlib to work with external processes in a portable way:

The program has to support the following two invocations:

goon -proto <major>.<minor> -ack <data>

goon -proto <major>.<minor> [-in] [-out] [-err <target>] [-dir <cwd>] \
    <program> [<arg>...]

The options are as follows:

  • -proto – protocol version number
  • -ack – used during Porcelain initialization to check if the protocol implementations are in sync between Elixir and Go
  • -in - boolean flag indicating whether goon should read stdin
  • -out - boolean flag indicating whether goon should write to stdout
  • -err - choose whether stderr should be redirected to stdout ("out") or discarded ("nil")
  • -dir – set current working directory for the launched program
  • <program> [<arg>...] – the invocation for the external program

The actual implementation should conform to the Goex protocol specification.

Clone this wiki locally