A module for writing Ceylon daemons that communicate over a system-provided socket.

Usage

The simplest way to write a daemon is to use one of the abstractions layered over this module, such as de.lucaswerkmeister.ceylond.recordBased for string records separated by a separator sequence like "\n" or ":", or de.lucaswerkmeister.ceylond.packetBased for packets with a length and (optionally) a type. You can also daemonize a full, existing program with de.lucaswerkmeister.ceylond.deamonizeProgram.

This module itself offers an asynchronous, callback-based server that sends and receives bytes. Each instance mainly consists of a callback that is called whenever data is read from the socket; it can also write to the socket and register a callback to be called when the write is finished. Per-instance error handling may also be configured.

This implements a basic ���cat��� server which simply sends back everything it receives:

shared void run() => start {
    function instance(void write(ByteBuffer content, WriteCallback callback), void close()) {
        void read(ByteBuffer content) {
            write(content, noop);
        }
        return [read, logAndAbort(`module`)];
    }
    fd = 3;
};

For details, please see the documentation of start() and its parameters.

The server process should inherit a socket file descriptor from the process that launched it, e.���g. the inetd superserver (or a derivative like xinetd) or the systemd system manager. The choice of file descriptor (the fd parameter above) depends on the parent process. For the JS backend, Node must be started directly (not via ceylon run-js); for the JVM backend, ceylon run may also be avoided through ceylon fat-jar.

The following systemd unit files may be used as a baseline for daemon unit files (placed, for example, in /etc/systemd/system/):

# ceylond.service
[Service]
# JVM backend
ExecStart=/usr/bin/java -jar /path/to/ceylond.jar
# JS backend
ExecStart=/usr/bin/node -e "require('de/lucaswerkmeister/ceylond/1.0.0/de.lucaswerkmeister.ceylond-1.0.0').run()"
Environment=NODE_PATH=/path/to/node_modules

# ceylond.socket
[Socket]
ListenStream=/var/run/ceylond/ceylond.sock

The daemon can then be activated with systemctl start ceylond.socket and permanently enabled with systemctl enable ceylond.socket.

Logging

This module (and its companion modules, listed above) makes use of ceylon.logging. If you register a log writer, it will also receive messages from this module.

This module also includes a log writer that emits messages to standard error in a format recognized by the systemd journal: writeSystemdLog(). When running under systemd, this format is preferred to ceylon.logging���s default writer.

Backend-specific limitations

  • On the JVM, this module looks at IOException error messages to determine their cause. Java localizes those messages, so it���s recommended to run Java with LC_MESSAGES=C.
  • On Node.js, connections to non-concurrent servers that are attempted while the server is handling another connection are not accepted once the server becomes ready again ��� only connections that are newly made when the server is free succeed.
  • File descriptor support varies across backends; see the documentation of fd for details.
Platform: JavaScript, Java
Packages
de.lucaswerkmeister.ceylond.core

The main package.

Dependencies
ceylon.buffer1.3.1
ceylon.collection1.3.1
ceylon.interop.java (jvm)1.3.1
ceylon.logging1.3.1
java.base (jvm)7

The main package.

Aliases
ReadCallbackshared ReadCallback=> Anything(ByteBuffer)

A read callback, to be called with a ready-to-read ByteBuffer when data has been read from the socket.

ServerExceptionHandlershared ServerExceptionHandler=> Boolean(ServerException)

A handler for server exceptions. The return value determines whether the server proceeds or not; true means to continue running and accepting connections if possible, false means to stop the server. If the server cannot continue even though the handler requests it, a warning is logged.

SocketExceptionHandlershared SocketExceptionHandler=> Boolean(SocketException)

A handler for socket exceptions. The return value determines whether the socket proceeds or not; true means to continue reading and writing on this socket, false means to close it. If the socket cannot continue even though the handler requests it, a warning is logged.

WriteCallbackshared WriteCallback=> Anything()

A write callback, to be called once a write is fully completed. (There is no count of bytes written; this library (on JS: Node itself) takes care of repeating writes until they���re completely done.)

Functions
logAndAbortshared ServerExceptionHandler&SocketExceptionHandler logAndAbort(Category category = ...)

A simple default error handler for both server and socket errors. It logs the exception on a logger for the given category (level error for socket exceptions and fatal for server exceptions) and then returns false to abort the connection or the server.

The category argument defaults to this module; you probably want to use your own module instead, like this:

value handler = logAndAbort(`module`);
Parameters:
  • category = `module`
startshared void start([ReadCallback, SocketExceptionHandler]? instance(void write(ByteBuffer content, WriteCallback callback), void close()), Integer fd, ServerExceptionHandler handler = ..., Boolean concurrent = true)

Start listening on the socket.

VERY IMPORTANT NOTE: You must return from the main program (run) after calling this function; on Node.js, the socket will not receive any data until control flow has returned to the main event loop. (On the JVM, the socket is handled in a separate thread, but you should return from the main program nonetheless.) You may call start() multiple times with different file descriptors to listen on multiple sockets, and you may also log messages or do other stuff after calling start(), but you must not enter any long-running activities or even an infinite loop, otherwise the sockets won���t work.

Parameters:
  • instance

    This function is called whenever a new connection to the socket is opened; it receives a function that can be used to write to the socket, and a function to close it. It returns two functions, one that is called whenever there is new data on the socket and one that handles exceptions on this socket, or null to signal that the socket should stop listening.

  • fd

    The file descriptor to listen on.

    For inetd and derivatives (e.���g. xinetd), this should be 0. systemd by default assigns the first socket to file descriptor 3, but also supports multiple sockets, which it assigns to subsequent file descriptors (4, 5, ���).

    The JVM only supports file descriptor 0 natively; if another file descriptor is specified, a channel object for it is obtained via reflection, in a manner that probably only works on OpenJDK.

    Node.js allows listening on any file descriptor natively, except file descriptor 0, where it fails with inexplicable errors without any useful stack trace.

    Thus, the choice of file descriptor to listen on depends on several factors:

    • If your program uses multiple sockets, use file descriptors 3 & seq., and accept that the program might not run on some JVM implementations.
    • If your program only needs to run on the JVM, use file descriptor 0, and add StandardInput=socket to your systemd service file.
    • If your program only needs to run on JS, use file descriptor 3.
    • If your program needs to run on both backends, and you don���t want to make the choice of file descriptor backend-specific, use file descriptor 3, and accept that the program might not run on some JVM implementations.
  • handler = logAndAbort()
  • concurrent = true

    Whether to allow concurrent connections or not.

    If true, every connection is accepted as soon as possible. If false, a new connection is only accepted once the previous one has terminated; this is useful if your program changes some global state, and multiple instances running concurrently may disturb each other.

writeSystemdLogshared void writeSystemdLog(Priority priority, Category category, String message, Throwable? throwable)

A log writer function that prints messages to standard error, prefixed with the priority in a format that the systemd journal interprets as log level (see sd-daemon(3)). (The timestamp is not included because that���s the journal���s job.)

This log writer function must be registered explicitly by calling:

addLogWriter(writeSystemdLog);
Exceptions
FileDescriptorInvalidExceptionshared FileDescriptorInvalidException

The file descriptor passed to start() is invalid for the current backend.

ReadCallbackExceptionshared ReadCallbackException

A wrapper for an exception that was thrown from a read callback.

ServerExceptionshared abstract ServerException

An exception that occurs on the server socket.

ServerSetupExceptionshared ServerSetupException

An exception that occurs during server setup. Exceptions of this kind cannot be ignored; the server cannot start.

SocketClosedExceptionshared SocketClosedException

The socket is closed, e.���g. because the other side closed it or because the connection broke. Exceptions of this kind cannot be ignored; the socket is closed.

Note that on the JVM, it���s not always possible to determine whether an IOException means a closed socket; some may be misclassified as an UnknownSocketException. Unless you can extract more information from the unknown exception than this module can, it���s safest to treat the two as equivalent.

SocketExceptionshared abstract SocketException

An exception that occurs on an individual connection socket.

SocketSetupExceptionshared SocketSetupException

An exception that occurs during socket setup. (This is conceptually more a SocketException than a ServerException, but these errors can occur before the instance function could return a socket exception handler.)

UnknownServerExceptionshared UnknownServerException

An unknown server exception.

UnknownSocketExceptionshared UnknownSocketException

An unknown exception that occurred during a socket operation.

It is strongly recommended to abort on these exceptions (return false from the error handler), since on the JVM they cannot reliably be distinguished from a SocketClosedException.

WriteCallbackExceptionshared WriteCallbackException

A wrapper for an exception that was thrown from a write callback.