Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 300 lines (221 sloc) 9.318 kb
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
1 #!/usr/bin/env ruby
2 # Author: Greg Brockman <gdb@stripe.com>
3
4 require 'rubygems'
5 require 'einhorn'
6
7 module Einhorn
8 module Executable
9 def self.einhorn_usage(long)
10 usage = <<EOF
11 ## Usage
12
13 Einhorn is the language-independent shared socket manager. Run
14 `einhorn -h` to see detailed usage. At a high level, usage looks like
15 the following:
16
17 einhorn [options] program
18
19 Einhorn will open one or more shared sockets and run multiple copies
20 of your process. You can seamlessly reload your code, dynamically
21 reconfigure Einhorn, and more.
22 EOF
23
24 if long
25 usage << <<EOF
26
27 ## Overview
28
29 To set Einhorn up as a master process running 3 copies of `sleep 5`:
30
31 $ einhorn -n 3 sleep 5
32
33 You can communicate your running Einhorn process via `einhornsh`:
34
35 $ einhornsh
36 Welcome gdb! You are speaking to Einhorn Master Process 11902
37 Enter 'help' if you're not sure what to do.
38
39 Type "quit" or "exit" to quit at any time
40 > help
41 You are speaking to the Einhorn command socket. You can run the following commands:
42 ...
43
44 ### Server sockets
45
46 If your process is a server and listens on one or more sockets,
1be724c Greg Brockman Rename -b option to -g; nuke useless safety checks
gdb authored
47 Einhorn can open these sockets and pass them to the workers. You can
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
48 specify the addresses to bind by passing one or more `-b ADDR`
49 arguments:
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
50
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
51 einhorn -b 127.0.0.1:1234 my-command
52 einhorn -b 127.0.0.1:1234,r -b 127.0.0.1:1235 my-command
1be724c Greg Brockman Rename -b option to -g; nuke useless safety checks
gdb authored
53
54 Each address is specified as an ip/port pair, possibly accompanied by options:
55
56 ADDR := (IP:PORT)[<,OPT>...]
57
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
58 In the worker process, the opened file descriptors will be represented
59 as a space-separated list of file descriptor numbers in the
60 EINHORN_FDS environment variable (respecting the order that the `-b`
61 options were provided in):
1be724c Greg Brockman Rename -b option to -g; nuke useless safety checks
gdb authored
62
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
63 EINHORN_FDS="6" # 127.0.0.1:1234
64 EINHORN_FDS="6 7" # 127.0.0.1:1234,r 127.0.0.1:1235
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
65
66 Valid opts are:
67
68 r, so_reuseaddr: set SO_REUSEADDR on the server socket
69 n, o_nonblock: set O_NONBLOCK on the server socket
70
71 You can for example run:
72
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
73 $ einhorn -b 127.0.0.1:2345,r -m manual -n 4 -- example/time_server
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
74
75 Which will run 4 copies of
76
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
77 EINHORN_FDS=6 example/time_server
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
78
79 Where file descriptor 6 is a server socket bound to `127.0.0.1:2345`
80 and with `SO_REUSEADDR` set. It is then your application's job to
81 figure out how to `accept()` on this file descriptor.
82
83 ### Command socket
84
85 Einhorn opens a UNIX socket to which you can send commands (run
86 `help` in `einhornsh` to see what admin commands you can
87 run). Einhorn relies on file permissions to ensure that no malicious
88 users can gain access. Run with a `-d DIRECTORY` to change the
89 directory where the socket will live.
90
665ce7b Greg Brockman Switch from JSON -> YAML for the command-socket protocol
gdb authored
91 Note that the command socket uses a line-oriented YAML protocol, and
92 you should ensure you trust clients to send arbitrary YAML messages
93 into your process.
94
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
95 ### Seamless upgrades
96
97 You can cause your code to be seamlessly reloaded by upgrading the
98 worker code on disk and running
99
100 $ einhornsh
101 ...
102 > upgrade
103
104 Once the new workers have been spawned, Einhorn will send each old
105 worker a SIGUSR2. SIGUSR2 should be interpreted as a request for a
106 graceful shutdown.
107
108 ### ACKs
109
110 After Einhorn spawns a worker, it will only consider the worker up
111 once it has received an ACK. Currently two ACK mechanisms are
112 supported: manual and timer.
113
114 #### Manual ACK
115
116 A manual ACK (configured by providing a `-m manual`) requires your
117 application to send a command to the command socket once it's
118 ready. This is the safest ACK mechanism. If you're writing in Ruby,
119 just do
120
121 require 'einhorn/worker'
122 Einhorn::Worker.ack!
123
124 in your worker code. If you're writing in a different language, or
125 don't want to include Einhorn in your namespace, you can send the
126 string
127
128 {"command":"worker:ack", "pid":PID}
129
130 to the UNIX socket pointed to by the environment variable
131 `EINHORN_SOCK_PATH`. (Be sure to include a trailing newline.)
132
ba1a2d6 Greg Brockman Update docs and examples for environment variable interface
gdb authored
133 To make things even easier, you can pass a `-g` to Einhorn, in which
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
134 case you just need to `write()` the above message to the open file
1295b40 Greg Brockman Change from EINHORN_FD -> EINHORN_SOCK_FD
gdb authored
135 descriptor pointed to by `EINHORN_SOCK_FD`.
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
136
137 (See `lib/einhorn/worker.rb` for details of these and other socket
138 discovery mechanisms.)
139
140 #### Timer ACK [default]
141
142 By default, Einhorn will use a timer ACK of 1 second. That means that
143 if your process hasn't exited after 1 second, it is considered ACK'd
144 and healthy. You can modify this timeout to be more appropriate for
145 your application (and even set to 0 if desired). Just pass a `-m
146 FLOAT`.
147
148 ### Preloading
149
150 If you're running a Ruby process, Einhorn can optionally preload its
151 code, so it only has to load the code once per upgrade rather than
152 once per worker process. This also saves on memory overhead, since all
153 of the code in these processes will be stored only once using your
154 operating system's copy-on-write features.
155
156 To use preloading, just give Einhorn a `-p PATH_TO_CODE`, and make
157 sure you've defined an `einhorn_main` method.
158
159 In order to maximize compatibility, we've worked to minimize Einhorn's
665ce7b Greg Brockman Switch from JSON -> YAML for the command-socket protocol
gdb authored
160 dependencies. It has no dependencies outside of the Ruby standard
161 library.
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
162
163 ### Command name
164
165 You can set the name that Einhorn and your workers show in PS. Just
166 pass `-c <name>`.
167 EOF
168 end
169
170 usage << <<EOF
171
172 ### Options
173
174 EOF
175 end
176 end
177 end
178
179 # Would be nice if this could be loadable rather than always
180 # executing, but when run under gem it's a bit hard to do so.
181 if true # $0 == __FILE__
182 Einhorn::TransientState.script_name = $0
183 Einhorn::TransientState.argv = ARGV.dup
9e09420 Evan Broder Store the environment at startup and restore it before reexecing
ebroder authored
184 Einhorn::TransientState.environ = ENV.to_hash
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
185
186 optparse = OptionParser.new do |opts|
0acfae8 Greg Brockman Add bind option
gdb authored
187 opts.on('-b ADDR', '--bind ADDR', 'Bind an address and add the corresponding FD to EINHORN_FDS') do |addr|
188 unless addr =~ /\A([^:]+):(\d+)((?:,\w+)*)\Z/
189 raise "Invalid value for #{addr.inspect}: bind address must be of the form address:port[,flags...]"
190 end
191
192 host = $1
193 port = Integer($2)
194 flags = $3.split(',').select {|flag| flag.length > 0}.map {|flag| flag.downcase}
195 Einhorn::State.bind << [host, port, flags]
196 end
197
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
198 opts.on('-c CMD_NAME', '--command-name CMD_NAME', 'Set the command name in ps to this value') do |cmd_name|
199 Einhorn::State.cmd_name = cmd_name
200 end
201
202 opts.on('-d PATH', '--socket-path PATH', 'Where to open the Einhorn command socket') do |path|
203 Einhorn::State.socket_path = path
204 end
205
206 opts.on('-e PIDFILE', '--pidfile PIDFILE', 'Where to write out the Einhorn pidfile') do |pidfile|
207 Einhorn::State.pidfile = pidfile
208 end
209
210 opts.on('-f LOCKFILE', '--lockfile LOCKFILE', 'Where to store the Einhorn lockfile') do |lockfile|
211 Einhorn::State.lockfile = lockfile
212 end
213
1be724c Greg Brockman Rename -b option to -g; nuke useless safety checks
gdb authored
214 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
215 Einhorn::State.command_socket_as_fd = true
216 end
217
3cc1e1e Greg Brockman Initial import of Einhorn
gdb authored
218 opts.on('-h', '--help', 'Display this message') do
219 opts.banner = Einhorn::Executable.einhorn_usage(true)
220 puts opts
221 exit(1)
222 end
223
224 opts.on('-k', '--kill-children-on-exit', 'If Einhorn exits unexpectedly, gracefully kill all its children') do
225 Einhorn::State.kill_children_on_exit = true
226 end
227
228 opts.on('-l', '--backlog N', 'Connection backlog (assuming this is a server)') do |b|
229 Einhorn::State.config[:backlog] = b.to_i
230 end
231
232 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|
233 # Try manual
234 if mode == 'manual'
235 Einhorn::State.ack_mode = {:type => :manual}
236 next
237 end
238
239 # Try float
240 begin
241 parsed = Float(mode)
242 rescue ArgumentError
243 else
244 Einhorn::State.ack_mode = {:type => :timer, :timeout => parsed}
245 next
246 end
247
248 # Give up
249 raise "Invalid ack-mode #{mode.inspect} (valid modes: FLOAT or manual)"
250 end
251
252 opts.on('-n', '--number N', 'Number of copies to spin up') do |n|
253 Einhorn::State.config[:number] = n.to_i
254 end
255
256 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|
257 Einhorn::State.path = path
258 end
259
260 opts.on('-q', '--quiet', 'Make output quiet (can be reconfigured on the fly)') do
261 Einhorn::Command.louder(false)
262 end
263
264 opts.on('-s', '--seconds N', 'Number of seconds to wait until respawning') do |b|
265 Einhorn::State.config[:seconds] = s.to_i
266 end
267
268 opts.on('-v', '--verbose', 'Make output verbose (can be reconfigured on the fly)') do
269 Einhorn::Command.louder(false)
270 end
271
272 opts.on('--with-state-fd STATE', '[Internal option] With file descriptor containing state') do |fd|
273 read = IO.for_fd(Integer(fd))
274 state = read.read
275 read.close
276
277 Einhorn.restore_state(state)
278 end
279
280 opts.on('--version', 'Show version') do
281 puts Einhorn::VERSION
282 exit
283 end
284 end
285 optparse.order!
286
287 if ARGV.length < 1
288 optparse.banner = Einhorn::Executable.einhorn_usage(false)
289 puts optparse
290 exit(1)
291 end
292
293 ret = Einhorn.run
294 begin
295 exit(ret)
296 rescue TypeError
297 exit(0)
298 end
299 end
Something went wrong with that request. Please try again.