Git Product home page Git Product logo

ceylond's Introduction

ceylond

A collection of modules to write daemons that communicate over a system-provided socket in Ceylon.

Short example

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

import de.lucaswerkmeister.ceylond.core { ... }

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;
};

You can build it for either backend:

# JVM
ceylon compile,fat-jar com.example.cat
# JS
ceylon compile-js com.example.cat
ceylon copy --js --with-dependencies --include-language --out node_modules com.example.cat

and run it, for example, with systemd-socket-activate:

systemd-socket-activate \
    -E LC_ALL=en_US.UTF-8 \
    -E LC_MESSAGES=C \
    --listen /run/cat \
    # JVM
    /usr/bin/java -jar com.example.cat-1.0.0.jar
    # JS
    /usr/bin/node -e "require('com/example/cat/1.0.0/com.example.cat-1.0.0').run()

Motivation

Both current backends for the Ceylon programming language, JVM and JS, run on highly sophisticated virtual machines. Those virtual machines have a non-negligible startup cost (over 50 μs) and optimize the programs they run over time as they learn more about the runtime behavior of the programs. For both these reasons, they are not ideal for short-lived, frequently-run programs, such as programs intended for command line or shell script usage.

Instead of abandoning the advantages of the virtual machines, we can set up a server version of the programs. The server runs continuously and listens on a socket (e. g. a Unix Domain Socket); each time a connection is made, it runs the original program, then returns to listening for the next connection. The client then, instead of running the program, simply needs to communicate with the server over the socket, a task which is readily implemented in a systems language like C. We get the best of both worlds: there is almost no startup overhead (the C client is quick to start, and the server is already running), and the server can still benefit from advanced optimizations by the virtual machine, since it can run for hours, days, or even months, continuously serving new connections.

Background

On Unix, network communication is done using a socket, which is a file descriptor created by the socket(2) system call and bound to an address using the bind(2) system call. A server applications marks the socket as a passive one with listen(2) and then repeatedly accept(2)s connections on it. Each accept(2) call returns a new file descriptor for the connection, which the server may then read(2) from and write(2) to just like any other file descriptor.

Since the socket is just a file descriptor, it is inherited by new child processes. It is thus possible to set up a socket in a parent program and the pass it to a potentially less privileged child program, which then accepts connections on the socket and handles then. In this pattern, the parent program is called a super-server. The pattern was first popularized by the inetd daemon (a modern derivative that may still be found in many GNU/Linux distributions is xinetd), but is today also employed by systemd under the moniker of socket activation. Under this schema, the service manager (system or user instance) sets up the socket and spawns a service which inherits the socket file descriptor (usually as file descriptor 3) as soon as a connection to the socket is made.

This makes it possible to use less common address families, like Unix Domain Sockets (AF_UNIX – the address is a file system entry), in programming languages that don’t directly support those address families, as long as they can deal with an inherited socket file descriptor: the socket is then simply set up by the system (inetd, systemd, …).

The ceylond modules

  • de.lucaswerkmeister.ceylond.core: This module handles the socket setup itself, as described in the last paragraph of the Background section, on both backends. It implements an asynchronous byte stream server (a synchronous one is not possible on Node.js): one registers a callback that is called whenever some data to read arrives, and can write data without waiting for the write to go through (instead registering another callback to be invoked when the write is done). The only data format offered is raw bytes, and while the module does ensure that no data is lost, there is no concept of messages or packets: boundaries between reads or writes may be lost through fragmentation or reassembly.
  • de.lucaswerkmeister.ceylond.recordBased: One of two abstractions on top of the core module. The data format is no longer bytes, but text (via a user-specified encoding, by default UTF-8). The text consists of records, separated by a record separator, which is a user-specified string. The default record separator is a single newline character, "\n", making each record one line and providing a line-based daemon. Such a daemon can easily be used from the command line with tools like nc (netcat) and socat (socket cat). Many internet protocols, including HTTP and SMTP, also use a similar format, but with "\r\n" as the record separator; see the module documentation for a simple HTTP server.
  • de.lucaswerkmeister.ceylond.packetBased: The other abstraction on top of the core module. A packet is a series of bytes, optionally annotated with an integer type. When writing a packet, the module adds the packet length and type; when reading a packet, the module parses the length and type and then reads data until the packet is complete, finally delivering a complete packet to the application. The size of the packet length and type are configurable (a type size of 0 bytes disables the type mechanism completely), and a maximum packet length may also be imposed to prevent malicious clients causing the server to allocate large amounts of memory.
  • de.lucaswerkmeister.ceylond.daemonizeProgram: Finally, this implements the original motivation on top of the packetBased module: You supply a run function, and this module turns it into a daemon as described in the Motivation section, taking care of process arguments, standard I/O, and some other mechanisms – see the module documentation for details. A sample client is also provided in the client/ subdirectory. (Note that standard input, if used, must be sent to the daemon before the program starts; interactive dialogs or similar interfaces must be emulated by the client.)

License

The content of this repository is released under the LGPLv3 as provided in the LICENSE file that accompanied this code.

Additionally, the client/ subdirectory is released under CC0.

ceylond's People

Contributors

lucaswerkmeister avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ceylond's Issues

Support Language Server Protocol

@luolong suggested this on Reddit: add support for the Language Server Protocol. That is, write another module just like packetBased and recordBased, which reads and writes JSON-RPC messages.

  • The header part is just like HTTP: Foo: bar baz headers, separated by \r\n, terminated by \r\n, all encoded in ASCII. We parse the Content-Length and Content-Type headers.
  • The content part is JSON, encoded as specified in Content-Type (default: UTF-8), and with the length as specified in Content-Length (in bytes).

How do we map JSON-RPC to Ceylon? Do we just exchange ceylon.json objects with the client library, or do we perhaps do something fancier, e. g. automatically find the function of the right name in the client module and call it with the specified parameters? (I’m tempted to say that even if something fancier can be done, it should be left to a separate module, which would hopefully compose well with this one.)

Do we deliver batch requests to the client in batch or completely hide them from it and make the client process them one by one? The latter is probably much simpler.

Update daemonizeProgram for new process implementation

In the upcoming Ceylon 1.4, process is implemented in native Ceylon, so setProcessArguments needs to be updated.

See eclipse-archived/ceylon@223ce11 for the JVM implementation and eclipse-archived/ceylon@a46b7c9, eclipse-archived/ceylon@2e07c12, eclipse-archived/ceylon@20cc493, eclipse-archived/ceylon@8078705 and eclipse-archived/ceylon@5057623 for the JS implementation. (Okay, the JS list is getting a bit long… probably best to just look at the final result. I’ll also have missed some of the JS commits.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.