{"id":13878439,"url":"https://github.com/contribsys/einhorn","last_synced_at":"2025-12-16T18:37:20.888Z","repository":{"id":3390677,"uuid":"4439463","full_name":"contribsys/einhorn","owner":"contribsys","description":"Einhorn: the language-independent shared socket manager           ","archived":false,"fork":false,"pushed_at":"2025-04-10T16:49:01.000Z","size":464,"stargazers_count":1635,"open_issues_count":1,"forks_count":46,"subscribers_count":72,"default_branch":"main","last_synced_at":"2025-05-10T04:47:38.102Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/contribsys.png","metadata":{"files":{"readme":"README.md","changelog":"Changes.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2012-05-25T00:03:23.000Z","updated_at":"2025-05-06T11:55:49.000Z","dependencies_parsed_at":"2025-04-23T06:42:00.166Z","dependency_job_id":null,"html_url":"https://github.com/contribsys/einhorn","commit_stats":null,"previous_names":["stripe/einhorn"],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contribsys%2Feinhorn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contribsys%2Feinhorn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contribsys%2Feinhorn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contribsys%2Feinhorn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/contribsys","download_url":"https://codeload.github.com/contribsys/einhorn/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253365428,"owners_count":21897184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-06T08:01:49.746Z","updated_at":"2025-12-16T18:37:15.826Z","avatar_url":"https://github.com/contribsys.png","language":"Ruby","funding_links":[],"categories":["Ruby","DevOps Tools"],"sub_categories":[],"readme":"\n# Einhorn: the language-independent shared socket manager\n\nEinhorn makes it easy to run (and keep alive) multiple\ncopies of a single long-lived process. If that process is a server\nlistening on some socket, Einhorn will open the socket in the master\nprocess so that it's shared among the workers.\n\nEinhorn is designed to be compatible with arbitrary languages and\nframeworks, requiring minimal modification of your\napplication. Einhorn is simple to configure and run.\n\n## Installation\n\nInstall from Rubygems as:\n\n    $ gem install einhorn\n\nOr build from source by:\n\n    $ gem build einhorn.gemspec\n\nAnd then install the built gem.\n\n## Usage\n\nEinhorn is the language-independent shared socket manager. Run\n`einhorn -h` to see detailed usage. At a high level, usage looks like\nthe following:\n\n    einhorn [options] program\n\nEinhorn will open one or more shared sockets and run multiple copies\nof your process. You can seamlessly reload your code, dynamically\nreconfigure Einhorn, and more.\n\n## Overview\n\nTo set Einhorn up as a master process running 3 copies of `sleep 5`:\n\n    $ einhorn -n 3 sleep 5\n\nYou can communicate your running Einhorn process via `einhornsh`:\n\n    $ einhornsh\n    Welcome! You are speaking to Einhorn Master Process 11902\n    Enter 'help' if you're not sure what to do.\n\n    Type \"quit\" or \"exit\" to quit at any time\n    \u003e help\n    You are speaking to the Einhorn command socket. You can run the following commands:\n    ...\n\n### Server sockets\n\nIf your process is a server and listens on one or more sockets,\nEinhorn can open these sockets and pass them to the workers. You can\nspecify the addresses to bind by passing one or more `-b ADDR`\narguments:\n\n    einhorn -b 127.0.0.1:1234 my-command\n    einhorn -b 127.0.0.1:1234,r -b 127.0.0.1:1235 my-command\n\nEach address is specified as an ip/port pair, possibly accompanied by options:\n\n    ADDR := (IP:PORT)[\u003c,OPT\u003e...]\n\nIn the worker process, the opened file descriptors will be represented\nas file descriptor numbers in a series of environment variables named\nEINHORN_FD_0, EINHORN_FD_1, etc. (respecting the order that the `-b`\noptions were provided in), with the total number of file descriptors\nin the EINHORN_FD_COUNT environment variable:\n\n    EINHORN_FD_0=\"6\" # 127.0.0.1:1234\n    EINHORN_FD_COUNT=\"1\"\n\n    EINHORN_FD_0=\"6\" # 127.0.0.1:1234,r\n    EINHORN_FD_1=\"7\" # 127.0.0.1:1235\n    EINHORN_FD_COUNT=\"2\"\n\nValid opts are:\n\n    r, so_reuseaddr: set SO_REUSEADDR on the server socket\n    n, o_nonblock: set O_NONBLOCK on the server socket\n\nYou can for example run:\n\n    $ einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server\n\nWhich will run 4 copies of\n\n    EINHORN_FD_0=6 EINHORN_FD_COUNT=1 example/time_server\n\nWhere file descriptor 6 is a server socket bound to `127.0.0.1:2345`\nand with `SO_REUSEADDR` set. It is then your application's job to\nfigure out how to `accept()` on this file descriptor.\n\n### Command socket\n\nEinhorn opens a UNIX socket to which you can send commands (run\n`help` in `einhornsh` to see what admin commands you can\nrun). Einhorn relies on file permissions to ensure that no malicious\nusers can gain access. Run with a `-d DIRECTORY` to change the\ndirectory where the socket will live.\n\nNote that the command socket uses a line-oriented YAML protocol, and\nyou should ensure you trust clients to send arbitrary YAML messages\ninto your process.\n\n### Seamless upgrades\n\nYou can cause your code to be seamlessly reloaded by upgrading the\nworker code on disk and running\n\n    $ einhornsh\n    ...\n    \u003e upgrade\n\nOnce the new workers have been spawned, Einhorn will send each old\nworker a SIGUSR2. SIGUSR2 should be interpreted as a request for a\ngraceful shutdown.\n\n### ACKs\n\nAfter Einhorn spawns a worker, it will only consider the worker up\nonce it has received an ACK. Currently two ACK mechanisms are\nsupported: manual and timer.\n\n#### Manual ACK\n\nA manual ACK (configured by providing a `-m manual`) requires your\napplication to send a command to the command socket once it's\nready. This is the safest ACK mechanism. If you're writing in Ruby,\njust do\n\n    require 'einhorn/worker'\n    Einhorn::Worker.ack!\n\nin your worker code. If you're writing in a different language, or\ndon't want to include Einhorn in your namespace, you can send the\nstring\n\n    {\"command\":\"worker:ack\", \"pid\":PID}\n\nto the UNIX socket pointed to by the environment variable\n`EINHORN_SOCK_PATH`. (Be sure to include a trailing newline.)\n\nTo make things even easier, you can pass a `-g` to Einhorn, in which\ncase you just need to `write()` the above message to the open file\ndescriptor pointed to by `EINHORN_SOCK_FD`.\n\n(See `lib/einhorn/worker.rb` for details of these and other socket\ndiscovery mechanisms.)\n\n#### Timer ACK [default]\n\nBy default, Einhorn will use a timer ACK of 1 second. That means that\nif your process hasn't exited after 1 second, it is considered ACK'd\nand healthy. You can modify this timeout to be more appropriate for\nyour application (and even set to 0 if desired). Just pass a `-m\nFLOAT`.\n\n### Preloading\n\nIf you're running a Ruby process, Einhorn can optionally preload its\ncode, so it only has to load the code once per upgrade rather than\nonce per worker process. This also saves on memory overhead, since all\nof the code in these processes will be stored only once using your\noperating system's copy-on-write features.\n\nTo use preloading, just give Einhorn a `-p PATH_TO_CODE`, and make\nsure you've defined an `einhorn_main` method.\n\nIn order to maximize compatibility, we've worked to minimize Einhorn's\ndependencies. It has no dependencies outside of the Ruby standard\nlibrary.\n\n### Command name\n\nYou can set the name that Einhorn and your workers show in PS. Just\npass `-c \u003cname\u003e`.\n\n### Re exec\n\nYou can use the `--reexec-as` option to replace the `einhorn` command with a command or script of your own. This might be useful for those with a Capistrano like deploy process that has changing symlinks. To ensure that you are following the symlinks you could use a bash script like this.\n\n    #!/bin/bash\n\n    cd \u003csymlinked directory\u003e\n    exec /usr/local/bin/einhorn \"$@\"\n\nThen you could set `--reexec-as=` to the name of your bash script and it will run in place of the plain einhorn command.\n\n### Options\n\n    -b, --bind ADDR                  Bind an address and add the corresponding FD via the environment\n    -c, --command-name CMD_NAME      Set the command name in ps to this value\n    -d, --socket-path PATH           Where to open the Einhorn command socket\n    -e, --pidfile PIDFILE            Where to write out the Einhorn pidfile\n    -f, --lockfile LOCKFILE          Where to store the Einhorn lockfile\n    -g, --command-socket-as-fd       Leave the command socket open as a file descriptor, passed in the EINHORN_SOCK_FD environment variable. This allows your worker processes to ACK without needing to know where on the filesystem the command socket lives.\n    -h, --help                       Display this message\n    -k, --kill-children-on-exit      If Einhorn exits unexpectedly, gracefully kill all its children\n    -l, --backlog N                  Connection backlog (assuming this is a server)\n    -m, --ack-mode MODE              What kinds of ACK to expect from workers. Choices: FLOAT (number of seconds until assumed alive), manual (process will speak to command socket when ready). Default is MODE=1.\n    -n, --number N                   Number of copies to spin up\n    -p, --preload PATH               Load this code into memory, and fork but do not exec upon spawn. Must define an \"einhorn_main\" method\n    -q, --quiet                      Make output quiet (can be reconfigured on the fly)\n    -s, --seconds N                  Number of seconds to wait until respawning\n    -v, --verbose                    Make output verbose (can be reconfigured on the fly)\n        --drop-env-var VAR_NAME      Delete VAR_NAME from the environment that is restored on upgrade\n        --reexec-as=CMDLINE          Substitute CMDLINE for \\\"einhorn\\\" when upgrading\n        --nice MASTER[:WORKER=0][:RENICE_CMD=/usr/bin/renice]\n                                     Unix nice level at which to run the einhorn processes. If not running as root, make sure to ulimit -e as appopriate.\n        --with-state-fd STATE        [Internal option] With file descriptor containing state\n        --upgrade-check              [Internal option] Check if Einhorn can exec itself and exit with status 0 before loading code\n    -t, --signal-timeout=T           If children do not react to signals after T seconds, escalate to SIGKILL\n        --version                    Show version\n\n\n## History\n\nEinhorn came about when Stripe was investigating seamless code\nupgrading solutions for our API worker processes. We really liked the\nprocess model of [Unicorn](http://unicorn.bogomips.org/), but didn't\nwant to use its HTTP functionality. So Einhorn was born, providing the\nmaster process functionality of Unicorn (and similar preforking\nservers) to a wider array of applications.\n\nRead more background at [Meet Einhorn](https://web.archive.org/web/20210507145921/https://stripe.com/blog/meet-einhorn).\n\nStripe currently uses Einhorn in production for a number of\nservices. You can use Conrad Irwin's thin-attach_socket gem along with\nEventMachine-LE to support file-descriptor passing. Check out\n`example/thin_example` for an example of running Thin under Einhorn.\n\n## Compatibility\n\nEinhorn runs in Ruby 2.5+.\n\nThe following libraries ease integration with Einhorn with languages other than\nRuby:\n\n- **[go-einhorn](https://github.com/stripe/go-einhorn)**: Stripe's own library\n  for *talking* to an einhorn master (doesn't wrap socket code).\n- **[goji](https://github.com/zenazn/goji/)**: Go (golang) server framework. The\n  [`bind`](https://godoc.org/github.com/zenazn/goji/bind) and\n  [`graceful`](https://godoc.org/github.com/zenazn/goji/graceful)\n  packages provide helpers and HTTP/TCP connection wrappers for Einhorn\n  integration.\n- **[github.com/CHH/einhorn](https://github.com/CHH/einhorn)**: PHP library\n- **[thin-attach\\_socket](https://github.com/ConradIrwin/thin-attach_socket)**:\n  run `thin` behind Einhorn\n- **[baseplate](https://reddit.github.io/baseplate/cli/serve.html)**: a\n  collection of Python helpers and libraries, with support for running behind\n  Einhorn\n\n*NB: this list should not imply any official endorsement or vetting!*\n\n## About\n\nEinhorn was a project of [Stripe](https://stripe.com).\nIt is now maintained by [Contributed Systems](https://contribsys.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontribsys%2Feinhorn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcontribsys%2Feinhorn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontribsys%2Feinhorn/lists"}