This module allows you to turn a regular Ceylon program into a daemonized version. On each connection, the daemon will accept command line arguments, a working directory, and standard input. It will then set up the environment for the program, and launch it. Afterwards, the program���s output and result are sent over the connection again. A simple client that just communicates with the daemon can then be used in place of the original program, eliminating VM startup time and taking advantage of VM optimizations for long-running programs. One example client may be found in the client/ directory of the source code repository of this module; if you wish to implement your own client, please consult the detailed protocol description in the makeDaemonizeProgramInstance() documentation.

import com.example.program { program=run }
import de.lucaswerkmeister.ceylond.daemonizeProgram { daemonizeProgram }

shared void run() => daemonizeProgram { program; fd = 3; };

The following mechanisms are trapped/replaced and may be used by the program:

  • standard input, output and error (but note that all standard input must be provided before the program starts)
  • exit
  • arguments, including named arguments
  • uncaught exceptions from the run function

The working directory of the daemon process remains unchanged, but a small handler may be registered to adapt paths in arguments as needed.

The following mechanisms are not trapped/replaced and should not be used by the program:

  • propertyValue
  • environmentVariableValue
  • any backend-specific mechanism

Logging

This module provides a custom version of the core module���s writeSystemdLog that does not interfere with trapped standard error: see writeSystemdLog().

Platform: JavaScript, Java
Packages
de.lucaswerkmeister.ceylond.daemonizeProgram

The main package.

Dependencies
ceylon.collection1.3.1
ceylon.interop.java (jvm)1.3.1
de.lucaswerkmeister.ceylond.packetBased1.0.0
java.base (jvm)7

The main package.

Functions
daemonizeProgramshared void daemonizeProgram(void run(), Integer fd, String[] argumentsMap(String[] arguments, String? workingDirectory) = ..., Integer lengthSize = ..., Integer typeSize = 1, Integer? maximumStandardInput = null, Integer? maximumStandardOutput = null, Integer? maximumStandardError = null, Integer maximumPacketLength = ...)

Turns a normal program (run()) into a daemon that runs one instance of the program per connection. Only one instance of the program is run at a time, to avoid interference between multiple instances via global state. (However, the program needs to take care of resetting such state at the beginning or end itself.)

Parameters:
makeDaemonizeProgramInstanceshared [ReadCallback, SocketExceptionHandler]? makeDaemonizeProgramInstance(void run(), String[] argumentsMap(String[] arguments, String? workingDirectory) = ..., Integer lengthSize = ..., Integer typeSize = 1, Integer? maximumStandardInput = null, Integer? maximumStandardOutput = null, Integer? maximumStandardError = null, Integer maximumPacketLength = ...)(void write(ByteBuffer content, WriteCallback callback), void close())

Create an instance for start that runs a given program as daemon.

The socket protocol is packet-based, with configurable lengthSize and typeSize. Each connection understands the following packet types:

  • #00 (0): launch the program. This must be the last packet; any other packets after this one are an error.
  • #01 (1): add an argument. The packet content is a single, UTF-8 encoded argument. Upon launch, all received arguments are sent to argumentsMap() (together with the working directory, if one was received ��� see below), and the result (by default, the arguments themselves) is made available to the program in arguments. The mapping function may, for example, turn relative file paths in the arguments into absolute ones, since the real working directory of the process is still the daemon working directory and cannot be changed.
  • #02 (2): set working directory. The working directory, if set, will be passed to argumentsMap() along with all arguments (otherwise, that argument will be null). This packet may only be sent at most once.
  • #03 (3): add standard input. The packet content is added to a buffer which, upon launch, is made available to the process as standard input via readLine and similar functions. No encoding is specified; language module functions producing text instead of bytes are themselves responsible for decoding the content, just as with regular standard input. (It is recommended to transfer text in UTF-8 encoding and ensure that the daemon process runs in a UTF-8 locale.) Note that the JS backend does not support reading from standard input, and this module does nothing to change that.

Once launched, the program runs to completion. Afterwards, the following packet types may be sent:

  • #80 (128): connection closes. The content is a 4-byte integer in network byte order, indicating the exit code (exit���s argument; process.exit throws if called with a number that does not fit in four bytes), or 0 if the run() function returned normally, or 1 if the run() function threw an exception, or #7FFFFFF0 (2147483632) if standard input was exceeded (see maximumStandardInput), or #7FFFFFF1 (2147483633) if standard output was exceeded (see maximumStandardOutput), or #7FFFFFF2 (2147483634) if standard error was exceeded (see maximumStandardError). A packet of this type is always sent on termination (unless the packet-based protocol itself is violated), and it is always the last packet; the socket is closed once this packet has been sent.
  • #81 (129): standard output. All standard output produced by the program via writeLine and similar functions is stored in a buffer and finally sent in a packet of this type. Boundaries between writes are not preserved. (On the JVM backend, no encoding is specified, and the same advice as for standard input (#03, see above) applies; on the JS backend, the buffer is UTF-8 encoded.)
  • #82 (130): standard error. Analogous to standard output (#81, see above).
  • #83 (131): exception stacktrace. If the program threw an exception, a packet of this type is sent, containing the UTF-8 encoded stack trace.
  • #90 (144): standard input too long. This packet is sent when the a received standard input packet bumps the total number of standard input bytes received above the application-configured limit. It contains that limit as a 4-byte integer in network byte order.
  • #91 (145): standard output too long. This packet is sent when a write to standard output by the application exceeds the application-configured limit. It contains that limit as a 4-byte integer in network byte order.
  • #92 (146): standard error too long. Analogous to standard output too long (#91, see above).
Parameters:
  • run

    The program being daemonized.

  • argumentsMap = arguments

    A mapping function that is applied to the arguments before they are stored in arguments. If a working directory packet has been sent, that is set as the second argument. This function can, for example, change relative paths in the arguments to absolute ones (relative to the passed working directory) to ensure they are valid in the program when it runs under the daemon���s working directory.

  • lengthSize = 2

    The size of the packet length. By default, a conservative size of 2 is chosen, but I/O heavy programs may require larger sizes (all output is sent in a single packet).

  • typeSize = 1

    The size of the type length. The default of 1 is sufficient for all understood types, but a higher value may be desirable for alignment purpose.

  • maximumStandardInput = null

    The maximum number of bytes accepted on standard input. If this is not null and is exceeded by some standard input packet, the daemon sends ���standard input exceeded��� (#90) and ���exit��� (#80) packets and then closes the connection. (Truncating the input in a meaningful way, if desired, is then the responsibility of the client program.)

  • maximumStandardOutput = null

    The maximum number of bytes accepted on standard output. If this is not null and exceeded by some write to standard output, an exception is immediately thrown. If the run() function does not catch this error, the program is terminated; the daemon sends ���standard output exceeded��� (#91) and ���exit��� (#80) packets and then closes the connection.

  • maximumStandardError = null

    The maximum number of bytes accepted on standard error. If this is not null and exceeded by some write to standard error, an exception is immediately thrown. If the run() function does not catch this error, the program is terminated; the daemon sends ���standard error exceeded��� (#92) and ���exit��� (#80) packets and then closes the connection.

  • maximumPacketLength = 256^lengthSize - 1

    See maximumLength.

writeSystemdLogshared Anything(Priority, Category, String, Throwable?) writeSystemdLog()

A version of writeSystemdLog that continues to log to the real standard error even after standard error has been redirected for the program. Usage:

addLogWriter(writeSystemdLog());

(Note that this function has one parameter list more than the original one; the first invocation captures the real standard error.)