Einhorn

repository·main·Indexed 23 days ago

https://github.com/contribsys/einhorn

A language-independent shared socket manager that acts as a master process to manage multiple copies of a long-lived worker process. It provides features for socket sharing, seamless code upgrades via SIGUSR2, and worker health monitoring through timer or manual ACKs. It includes the einhornsh utility for administrative commands and supports Ruby-specific preloading via the einhorn_main method.

Tokens
3.9K
Snippets
7
Records
25
Agent score
69%

What's inside einhorn

  1. Manage Einhorn via the Command Socket

    main

    Einhorn opens a UNIX socket for administrative commands. You can interact with it using the einhornsh utility.

    Configuration:

    • Use -d DIRECTORY to specify where the command socket file will be created.
    • The command socket uses a line-oriented YAML protocol. Ensure you trust clients, as they can send arbitrary YAML messages.

    Seamless Upgrades: To reload your code without downtime:

    1. Upgrade the worker code on disk.
    2. Run einhornsh and execute the upgrade command.

    Einhorn will spawn new workers and send a SIGUSR2 to old workers, which should be interpreted as a request for a graceful shutdown.

    einhornsh
    > upgrade
  2. Use Preloading for Ruby Processes

    main

    If running a Ruby process, you can use the -p PATH_TO_CODE option to preload code into the master process. This allows workers to be spawned via fork rather than exec, saving memory through copy-on-write and speeding up upgrades because code is only loaded once.

    Requirement: You must define an einhorn_main method in your code for preloading to work.

  3. Implement Worker ACKs (Manual and Timer)

    main

    Einhorn waits for an ACK (acknowledgment) from a worker before considering it healthy.

    Timer ACK (Default)

    By default, Einhorn uses a 1-second timer. If the process hasn't exited after 1 second, it is considered healthy. You can change this timeout using -m FLOAT (e.g., -m 0.5).

    Manual ACK

    For maximum safety, use -m manual. This requires the worker to explicitly tell Einhorn it is ready.

    In Ruby:

    require 'einhorn/worker'
    Einhorn::Worker.ack!

    In other languages: Send the following JSON string (with a trailing newline) to the UNIX socket path specified in the EINHORN_SOCK_PATH environment variable:

    {"command":"worker:ack", "pid":PID}

    Using File Descriptors: If you run Einhorn with the -g (--command-socket-as-fd) flag, you can simply write() the JSON message above to the file descriptor provided in the EINHORN_SOCK_FD environment variable.

    # Ruby example
    require 'einhorn/worker'
    Einhorn::Worker.ack!
    
    # Non-Ruby JSON example
    {"command":"worker:ack", "pid":PID}
  4. Configure Server Sockets in Einhorn

    main

    If your application is a server, Einhorn can open the listening sockets in the master process and pass the file descriptors to the workers.

    Specify addresses using the -b ADDR flag. The format for ADDR is (IP:PORT)[<,OPT>...].

    Available Options for ADDR:

    • r (or so_reuseaddr): Sets SO_REUSEADDR on the socket.
    • n (or o_nonblock): Sets O_NONBLOCK on the socket.

    Worker Environment Variables: When Einhorn passes sockets to workers, it provides them via environment variables:

    • EINHORN_FD_N: The file descriptor number for the $N^{th}$ socket (starting at 0).
    • EINHORN_FD_COUNT: The total number of sockets passed.

    Example: Running the following:

    $ einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server

    Will result in 4 workers receiving:

    EINHORN_FD_0=6 EINHORN_FD_COUNT=1

    (Where FD 6 is the socket bound to 127.0.0.1:2345 with SO_REUSEADDR). It is the application's responsibility to accept() on these descriptors.

    einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server
  5. Install Einhorn

    main

    You can install Einhorn from Rubygems or build it from source.

    From Rubygems:

    $ gem install einhorn

    From source:

    $ gem build einhorn.gemspec
    # Then install the resulting gem
    # No single command covers both, but these are the two methods:
    gem install einhorn
    gem build einhorn.gemspec
  6. Basic Usage of Einhorn

    main

    Einhorn is a master process that manages multiple copies of your application. The high-level usage pattern is:

    einhorn [options] program

    To run 3 copies of a command like sleep 5:

    $ einhorn -n 3 sleep 5
    einhorn -n 3 sleep 5
  7. Configure server sockets with -b

    main

    If your application is a server, Einhorn can open sockets and pass the file descriptors to your workers via environment variables.

    Use the -b ADDR flag to specify addresses. The format is IP:PORT[,OPT...].

    Environment Variables in Workers:

    • EINHORN_FD_N: The file descriptor number for the $N^{th}$ socket (respecting the order of -b arguments).
    • EINHORN_FD_COUNT: The total number of file descriptors provided.

    Available Socket Options:

    • r or so_reuseaddr: Set SO_REUSEADDR on the server socket.
    • n or o_nonblock: Set O_NONBLOCK on the server socket.

    Example:

    # Bind to 127.0.0.1:2345 with SO_REUSEADDR and run 4 workers
    einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server
  8. Configure worker ACKs (Acknowledgment)

    main

    Einhorn considers a worker 'up' only after receiving an ACK. You can configure this using the -m MODE flag.

    1. Manual ACK (-m manual)

    This is the safest method. The worker must explicitly tell Einhorn it is ready.

    In Ruby:

    require 'einhorn/worker'
    Einhorn::Worker.ack!

    In other languages: Send the following JSON string (with a trailing newline) to the UNIX socket path specified in the EINHORN_SOCK_PATH environment variable:

    {"command":"worker:ack", "pid":PID}

    Using File Descriptors: If you run Einhorn with the -g (--command-socket-as-fd) flag, you can simply write() the JSON message above to the file descriptor pointed to by the EINHORN_SOCK_FD environment variable.

    2. Timer ACK (-m FLOAT) [Default]

    Einhorn waits for a specified number of seconds. If the process hasn't exited after that time, it is considered healthy. The default is 1 second.

    Example:

    # Use a 5-second timer for ACK
    einhorn -m 5.0 my-program
  9. How Einhorn manages state and configuration

    main

    Einhorn uses two primary state modules: Einhorn::State (persistent state shared across processes) and Einhorn::TransientState (process-local state).

    Einhorn::State tracks long-lived information such as child processes, socket bindings, and configuration. It uses an AbstractState pattern that allows accessing state keys as if they were methods via method_missing.

    Einhorn::TransientState tracks information specific to the current process execution, such as whether the process is a :master or :worker, its current argv, and its environ (environment variables).

  10. Use the einhornsh CLI to interact with Einhorn

    main

    The einhornsh command provides an interactive shell or a way to execute single/multiple commands against a running Einhorn master process via its command socket.

    Interactive Mode

    By default, running einhornsh without the -e flag enters an interactive mode where you can type commands at a > prompt. You can exit by typing quit or exit.

    Non-Interactive Mode

    To execute a sequence of commands without entering the shell, use the -e flag. Commands in a sequence can be separated by semicolons (;).

  11. Perform seamless upgrades

    main

    To reload your code without downtime, upgrade the worker code on disk and then use the einhornsh command-line tool to trigger the upgrade:

    1. Update your application code.
    2. Run einhornsh.
    3. Type upgrade in the interactive prompt.

    Worker Lifecycle during Upgrade: When an upgrade is triggered, Einhorn spawns new workers. Once the new workers are ready, Einhorn sends a SIGUSR2 signal to each old worker. Your application should interpret SIGUSR2 as a request for a graceful shutdown.

    $ einhornsh
    > upgrade
  12. Run Einhorn as a master process

    main

    Einhorn is a language-independent shared socket manager. It opens shared sockets and runs multiple copies of your program as worker processes. This allows for seamless code reloads and dynamic reconfiguration.

    Basic usage pattern: einhorn [options] program