#!/usr/bin/env ruby # Author: Greg Brockman require 'rubygems' require 'einhorn' module Einhorn module Executable def self.einhorn_usage(long) usage = < help You are speaking to the Einhorn command socket. You can run the following commands: ... ### Server sockets If your process is a server and listens on one or more sockets, Einhorn can open these sockets and pass them to the workers. You can specify the addresses to bind by passing one or more `-b ADDR` arguments: einhorn -b 127.0.0.1:1234 my-command einhorn -b 127.0.0.1:1234,r -b 127.0.0.1:1235 my-command Each address is specified as an ip/port pair, possibly accompanied by options: ADDR := (IP:PORT)[<,OPT>...] In the worker process, the opened file descriptors will be represented as file descriptor numbers in a series of environment variables named EINHORN_FD_0, EINHORN_FD_1, etc. (respecting the order that the `-b` options were provided in), with the total number of file descriptors in the EINHORN_FD_COUNT environment variable: EINHORN_FD_0="6" # 127.0.0.1:1234 EINHORN_FD_COUNT="1" EINHORN_FD_0="6" # 127.0.0.1:1234,r EINHORN_FD_1="7" # 127.0.0.1:1235 EINHORN_FD_COUNT="2" Valid opts are: r, so_reuseaddr: set SO_REUSEADDR on the server socket n, o_nonblock: set O_NONBLOCK on the server socket You can for example run: $ einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server Which will run 4 copies of EINHORN_FD_0=6 EINHORN_FD_COUNT=1 example/time_server Where file descriptor 6 is a server socket bound to `127.0.0.1:2345` and with `SO_REUSEADDR` set. It is then your application's job to figure out how to `accept()` on this file descriptor. ### Command socket Einhorn opens a UNIX socket to which you can send commands (run `help` in `einhornsh` to see what admin commands you can run). Einhorn relies on file permissions to ensure that no malicious users can gain access. Run with a `-d DIRECTORY` to change the directory where the socket will live. Note that the command socket uses a line-oriented YAML protocol, and you should ensure you trust clients to send arbitrary YAML messages into your process. ### Seamless upgrades You can cause your code to be seamlessly reloaded by upgrading the worker code on disk and running $ einhornsh ... > upgrade Once the new workers have been spawned, Einhorn will send each old worker a SIGUSR2. SIGUSR2 should be interpreted as a request for a graceful shutdown. ### ACKs After Einhorn spawns a worker, it will only consider the worker up once it has received an ACK. Currently two ACK mechanisms are supported: manual and timer. #### Manual ACK A manual ACK (configured by providing a `-m manual`) requires your application to send a command to the command socket once it's ready. This is the safest ACK mechanism. If you're writing in Ruby, just do require 'einhorn/worker' Einhorn::Worker.ack! in your worker code. If you're writing in a different language, or don't want to include Einhorn in your namespace, you can send the string {"command":"worker:ack", "pid":PID} to the UNIX socket pointed to by the environment variable `EINHORN_SOCK_PATH`. (Be sure to include a trailing newline.) To make things even easier, you can pass a `-g` to Einhorn, in which case you just need to `write()` the above message to the open file descriptor pointed to by `EINHORN_SOCK_FD`. (See `lib/einhorn/worker.rb` for details of these and other socket discovery mechanisms.) #### Timer ACK [default] By default, Einhorn will use a timer ACK of 1 second. That means that if your process hasn't exited after 1 second, it is considered ACK'd and healthy. You can modify this timeout to be more appropriate for your application (and even set to 0 if desired). Just pass a `-m FLOAT`. ### Preloading If you're running a Ruby process, Einhorn can optionally preload its code, so it only has to load the code once per upgrade rather than once per worker process. This also saves on memory overhead, since all of the code in these processes will be stored only once using your operating system's copy-on-write features. To use preloading, just give Einhorn a `-p PATH_TO_CODE`, and make sure you've defined an `einhorn_main` method. In order to maximize compatibility, we've worked to minimize Einhorn's dependencies. It has no dependencies outside of the Ruby standard library. ### Command name You can set the name that Einhorn and your workers show in PS. Just pass `-c `. EOF end usage << < 0}.map {|flag| flag.downcase} Einhorn::State.bind << [host, port, flags] end opts.on('-c CMD_NAME', '--command-name CMD_NAME', 'Set the command name in ps to this value') do |cmd_name| Einhorn::State.cmd_name = cmd_name end opts.on('-d PATH', '--socket-path PATH', 'Where to open the Einhorn command socket') do |path| Einhorn::State.socket_path = path end opts.on('-e PIDFILE', '--pidfile PIDFILE', 'Where to write out the Einhorn pidfile') do |pidfile| Einhorn::State.pidfile = pidfile end opts.on('-f LOCKFILE', '--lockfile LOCKFILE', 'Where to store the Einhorn lockfile') do |lockfile| Einhorn::State.lockfile = lockfile end opts.on('-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.') do Einhorn::State.command_socket_as_fd = true end opts.on('-h', '--help', 'Display this message') do opts.banner = Einhorn::Executable.einhorn_usage(true) puts opts exit(1) end opts.on('-k', '--kill-children-on-exit', 'If Einhorn exits unexpectedly, gracefully kill all its children') do Einhorn::State.kill_children_on_exit = true end opts.on('-l', '--backlog N', 'Connection backlog (assuming this is a server)') do |b| Einhorn::State.config[:backlog] = b.to_i end opts.on('-m MODE', '--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.') do |mode| # Try manual if mode == 'manual' Einhorn::State.ack_mode = {:type => :manual} next end # Try float begin parsed = Float(mode) rescue ArgumentError else Einhorn::State.ack_mode = {:type => :timer, :timeout => parsed} next end # Give up raise "Invalid ack-mode #{mode.inspect} (valid modes: FLOAT or manual)" end opts.on('-n', '--number N', 'Number of copies to spin up') do |n| Einhorn::State.config[:number] = n.to_i end opts.on('-p PATH', '--preload PATH', 'Load this code into memory, and fork but do not exec upon spawn. Must define an "einhorn_main" method') do |path| Einhorn::State.path = path end opts.on('-q', '--quiet', 'Make output quiet (can be reconfigured on the fly)') do Einhorn::Command.quieter(false) end opts.on('-s', '--seconds N', 'Number of seconds to wait until respawning') do |b| Einhorn::State.config[:seconds] = s.to_i end opts.on('-v', '--verbose', 'Make output verbose (can be reconfigured on the fly)') do Einhorn::Command.louder(false) end opts.on('--nice MASTER[:WORKER=0][:RENICE_CMD=/usr/bin/renice]', 'Unix nice level at which to run the einhorn processes. If not running as root, make sure to ulimit -e as appopriate.') do |nice| master, worker, renice_cmd = nice.split(':') master = Integer(master) if master worker = Integer(worker) if worker Einhorn::State.nice[:master] = master if master Einhorn::State.nice[:worker] = worker if worker Einhorn::State.nice[:renice_cmd] = renice_cmd if renice_cmd end opts.on('--with-state-fd STATE', '[Internal option] With file descriptor containing state') do |fd| Einhorn::TransientState.stateful = true read = IO.for_fd(Integer(fd)) state = read.read read.close Einhorn.restore_state(state) end opts.on('--version', 'Show version') do puts Einhorn::VERSION exit end end optparse.order! if ARGV.length < 1 optparse.banner = Einhorn::Executable.einhorn_usage(false) puts optparse exit(1) end ret = Einhorn.run begin exit(ret) rescue TypeError exit(0) end end