einhorn 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in einhorn.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Stripe (info@stripe.com)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # Einhorn: the language-independent shared socket manager
2
+
3
+ ![Einhorn](https://stripe.com/img/blog/posts/meet-einhorn/einhorn.png)
4
+
5
+ Let's say you have a server process which processes one request at a
6
+ time. Your site is becoming increasingly popular, and this one process
7
+ is no longer able to handle all of your inbound connections. However,
8
+ you notice that your box's load number is low.
9
+
10
+ So you start thinking about how to handle more requests. You could
11
+ rewrite your server to use threads, but threads are a pain to program
12
+ against (and maybe you're writing in Python or Ruby where you don't
13
+ have true threads anyway). You could rewrite your server to be
14
+ event-driven, but that'd require a ton of effort, and it wouldn't help
15
+ you go beyond one core. So instead, you decide to just run multiple
16
+ copies of your server process.
17
+
18
+ Enter Einhorn. Einhorn makes it easy to run (and keep alive) multiple
19
+ copies of a single long-lived process. If that process is a server
20
+ listening on some socket, Einhorn will open the socket in the master
21
+ process so that it's shared among the workers.
22
+
23
+ Einhorn is designed to be compatible with arbitrary languages and
24
+ frameworks, requiring minimal modification of your
25
+ application. Einhorn is simple to configure and run.
26
+
27
+ ## Installation
28
+
29
+ Install from Rubygems as:
30
+
31
+ $ gem install einhorn
32
+
33
+ Or build from source by:
34
+
35
+ $ gem build einhorn.gemspec
36
+
37
+ And then install the built gem.
38
+
39
+ ## Usage
40
+
41
+ Einhorn is the language-independent shared socket manager. Run
42
+ `einhorn -h` to see detailed usage. At a high level, usage looks like
43
+ the following:
44
+
45
+ einhorn [options] program
46
+
47
+ Einhorn will open one or more shared sockets and run multiple copies
48
+ of your process. You can seamlessly reload your code, dynamically
49
+ reconfigure Einhorn, and more.
50
+
51
+ ## Overview
52
+
53
+ To set Einhorn up as a master process running 3 copies of `sleep 5`:
54
+
55
+ $ einhorn -n 3 sleep 5
56
+
57
+ You can communicate your running Einhorn process via `einhornsh`:
58
+
59
+ $ einhornsh
60
+ Welcome gdb! You are speaking to Einhorn Master Process 11902
61
+ Enter 'help' if you're not sure what to do.
62
+
63
+ Type "quit" or "exit" to quit at any time
64
+ > help
65
+ You are speaking to the Einhorn command socket. You can run the following commands:
66
+ ...
67
+
68
+ ### Server sockets
69
+
70
+ If your process is a server and listens on one or more sockets,
71
+ Einhorn can open these sockets and pass them to the workers. Program
72
+ arguments of the form
73
+
74
+ srv:(IP:PORT)[<,OPT>...]
75
+ --MY-OPT=srv:(IP:PORT)[<,OPT>...]
76
+
77
+ Will be interpreted as a request to open a server socket bound to
78
+ IP:PORT. The argument will be replaced with `FD` and `---MY-OPT=FD`,
79
+ respectively, where `FD` is the file descriptor number of the socket.
80
+ Valid opts are:
81
+
82
+ r, so_reuseaddr: set SO_REUSEADDR on the server socket
83
+ n, o_nonblock: set O_NONBLOCK on the server socket
84
+
85
+ You can for example run:
86
+
87
+ $ einhorn -m manual -n 4 example/time_server srv:127.0.0.1:2345,r
88
+
89
+ Which will run 4 copies of
90
+
91
+ example/time_server 6
92
+
93
+ Where file descriptor 6 is a server socket bound to `127.0.0.1:2345`
94
+ and with `SO_REUSEADDR` set. It is then your application's job to
95
+ figure out how to `accept()` on this file descriptor.
96
+
97
+ ### Command socket
98
+
99
+ Einhorn opens a UNIX socket to which you can send commands (run
100
+ `help` in `einhornsh` to see what admin commands you can
101
+ run). Einhorn relies on file permissions to ensure that no malicious
102
+ users can gain access. Run with a `-d DIRECTORY` to change the
103
+ directory where the socket will live.
104
+
105
+ ### Seamless upgrades
106
+
107
+ You can cause your code to be seamlessly reloaded by upgrading the
108
+ worker code on disk and running
109
+
110
+ $ einhornsh
111
+ ...
112
+ > upgrade
113
+
114
+ Once the new workers have been spawned, Einhorn will send each old
115
+ worker a SIGUSR2. SIGUSR2 should be interpreted as a request for a
116
+ graceful shutdown.
117
+
118
+ ### ACKs
119
+
120
+ After Einhorn spawns a worker, it will only consider the worker up
121
+ once it has received an ACK. Currently two ACK mechanisms are
122
+ supported: manual and timer.
123
+
124
+ #### Manual ACK
125
+
126
+ A manual ACK (configured by providing a `-m manual`) requires your
127
+ application to send a command to the command socket once it's
128
+ ready. This is the safest ACK mechanism. If you're writing in Ruby,
129
+ just do
130
+
131
+ require 'einhorn/worker'
132
+ Einhorn::Worker.ack!
133
+
134
+ in your worker code. If you're writing in a different language, or
135
+ don't want to include Einhorn in your namespace, you can send the
136
+ string
137
+
138
+ {"command":"worker:ack", "pid":PID}
139
+
140
+ to the UNIX socket pointed to by the environment variable
141
+ `EINHORN_SOCK_PATH`. (Be sure to include a trailing newline.)
142
+
143
+ To make things even easier, you can pass a `-b` to Einhorn, in which
144
+ case you just need to `write()` the above message to the open file
145
+ descriptor pointed to by `EINHORN_FD`.
146
+
147
+ (See `lib/einhorn/worker.rb` for details of these and other socket
148
+ discovery mechanisms.)
149
+
150
+ #### Timer ACK [default]
151
+
152
+ By default, Einhorn will use a timer ACK of 1 second. That means that
153
+ if your process hasn't exited after 1 second, it is considered ACK'd
154
+ and healthy. You can modify this timeout to be more appropriate for
155
+ your application (and even set to 0 if desired). Just pass a `-m
156
+ FLOAT`.
157
+
158
+ ### Preloading
159
+
160
+ If you're running a Ruby process, Einhorn can optionally preload its
161
+ code, so it only has to load the code once per upgrade rather than
162
+ once per worker process. This also saves on memory overhead, since all
163
+ of the code in these processes will be stored only once using your
164
+ operating system's copy-on-write features.
165
+
166
+ To use preloading, just give Einhorn a `-p PATH_TO_CODE`, and make
167
+ sure you've defined an `einhorn_main` method.
168
+
169
+ In order to maximize compatibility, we've worked to minimize Einhorn's
170
+ dependencies. At the moment Einhorn imports 'rubygems' and 'json'. (If
171
+ these turn out to be issues, we could probably find a workaround.)
172
+
173
+ ### Command name
174
+
175
+ You can set the name that Einhorn and your workers show in PS. Just
176
+ pass `-c <name>`.
177
+
178
+ ### Options
179
+
180
+ -b, --command-socket-as-fd Leave the command socket open as a file descriptor, passed in the EINHORN_FD environment variable. This allows your worker processes to ACK without needing to know where on the filesystem the command socket lives.
181
+ -c, --command-name CMD_NAME Set the command name in ps to this value
182
+ -d, --socket-path PATH Where to open the Einhorn command socket
183
+ -e, --pidfile PIDFILE Where to write out the Einhorn pidfile
184
+ -f, --lockfile LOCKFILE Where to store the Einhorn lockfile
185
+ -h, --help Display this message
186
+ -k, --kill-children-on-exit If Einhorn exits unexpectedly, gracefully kill all its children
187
+ -l, --backlog N Connection backlog (assuming this is a server)
188
+ -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.
189
+ -n, --number N Number of copies to spin up
190
+ -p, --preload PATH Load this code into memory, and fork but do not exec upon spawn. Must define an "einhorn_main" method
191
+ -q, --quiet Make output quiet (can be reconfigured on the fly)
192
+ -s, --seconds N Number of seconds to wait until respawning
193
+ -v, --verbose Make output verbose (can be reconfigured on the fly)
194
+ --with-state-fd STATE [Internal option] With file descriptor containing state
195
+ --version Show version
196
+
197
+
198
+ ## Contributing
199
+
200
+ Contributions are definitely welcome. To contribute, just follow the
201
+ usual workflow:
202
+
203
+ 1. Fork Einhorn
204
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
205
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
206
+ 4. Push to the branch (`git push origin my-new-feature`)
207
+ 5. Create new Github pull request
208
+
209
+ ## History
210
+
211
+ Einhorn came about when Stripe was investigating seamless code
212
+ upgrading solutions for our API worker processes. We really liked the
213
+ process model of [Unicorn](http://unicorn.bogomips.org/), but didn't
214
+ want to use its HTTP functionality. So Einhorn was born, providing the
215
+ master process functionality of Unicorn (and similar preforking
216
+ servers) to a wider array of applications.
217
+
218
+ See https://stripe.com/blog/meet-einhorn for more background.
219
+
220
+ Stripe currently uses Einhorn in production for a number of
221
+ services. Our Thin + EventMachine servers currently require patches to
222
+ both Thin and EventMachine (to support file-descriptor passing). You
223
+ can obtain these patches from our public forks of the
224
+ [respective](https://github.com/stripe/thin)
225
+ [projects](https://github.com/stripe/eventmachine). Check out
226
+ `example/thin_example` for an example of running Thin under Einhorn.
227
+
228
+ ## Compatibility
229
+
230
+ Einhorn was developed and tested under Ruby 1.8.7.
231
+
232
+ ## About
233
+
234
+ Einhorn is a project of [Stripe](https://stripe.com), led by [Greg
235
+ Brockman](https://twitter.com/thegdb). Feel free to get in touch at
236
+ info@stripe.com.
data/README.md.in ADDED
@@ -0,0 +1,79 @@
1
+ # Einhorn: the language-independent shared socket manager
2
+
3
+ ![Einhorn](https://stripe.com/img/blog/posts/meet-einhorn/einhorn.png)
4
+
5
+ Let's say you have a server process which processes one request at a
6
+ time. Your site is becoming increasingly popular, and this one process
7
+ is no longer able to handle all of your inbound connections. However,
8
+ you notice that your box's load number is low.
9
+
10
+ So you start thinking about how to handle more requests. You could
11
+ rewrite your server to use threads, but threads are a pain to program
12
+ against (and maybe you're writing in Python or Ruby where you don't
13
+ have true threads anyway). You could rewrite your server to be
14
+ event-driven, but that'd require a ton of effort, and it wouldn't help
15
+ you go beyond one core. So instead, you decide to just run multiple
16
+ copies of your server process.
17
+
18
+ Enter Einhorn. Einhorn makes it easy to run (and keep alive) multiple
19
+ copies of a single long-lived process. If that process is a server
20
+ listening on some socket, Einhorn will open the socket in the master
21
+ process so that it's shared among the workers.
22
+
23
+ Einhorn is designed to be compatible with arbitrary languages and
24
+ frameworks, requiring minimal modification of your
25
+ application. Einhorn is simple to configure and run.
26
+
27
+ ## Installation
28
+
29
+ Install from Rubygems as:
30
+
31
+ $ gem install einhorn
32
+
33
+ Or build from source by:
34
+
35
+ $ gem build einhorn.gemspec
36
+
37
+ And then install the built gem.
38
+
39
+ [[usage]]
40
+
41
+ ## Contributing
42
+
43
+ Contributions are definitely welcome. To contribute, just follow the
44
+ usual workflow:
45
+
46
+ 1. Fork Einhorn
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Github pull request
51
+
52
+ ## History
53
+
54
+ Einhorn came about when Stripe was investigating seamless code
55
+ upgrading solutions for our API worker processes. We really liked the
56
+ process model of [Unicorn](http://unicorn.bogomips.org/), but didn't
57
+ want to use its HTTP functionality. So Einhorn was born, providing the
58
+ master process functionality of Unicorn (and similar preforking
59
+ servers) to a wider array of applications.
60
+
61
+ See https://stripe.com/blog/meet-einhorn for more background.
62
+
63
+ Stripe currently uses Einhorn in production for a number of
64
+ services. Our Thin + EventMachine servers currently require patches to
65
+ both Thin and EventMachine (to support file-descriptor passing). You
66
+ can obtain these patches from our public forks of the
67
+ [respective](https://github.com/stripe/thin)
68
+ [projects](https://github.com/stripe/eventmachine). Check out
69
+ `example/thin_example` for an example of running Thin under Einhorn.
70
+
71
+ ## Compatibility
72
+
73
+ Einhorn was developed and tested under Ruby 1.8.7.
74
+
75
+ ## About
76
+
77
+ Einhorn is a project of [Stripe](https://stripe.com), led by [Greg
78
+ Brockman](https://twitter.com/thegdb). Feel free to get in touch at
79
+ info@stripe.com.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ desc 'Rebuild the README with the latest usage from einhorn'
6
+ task :readme do
7
+ Dir.chdir(File.dirname(__FILE__))
8
+ readme = File.read('README.md.in')
9
+ usage = `bin/einhorn -h`
10
+ readme.gsub!('[[usage]]', usage)
11
+ File.open('README.md', 'w') {|f| f.write(readme)}
12
+ end
13
+
14
+ Rake::TestTask.new do |t|
15
+ t.libs = ["lib"]
16
+ # t.warning = true
17
+ t.verbose = true
18
+ t.test_files = FileList['test/unit/**/*.rb']
19
+ end
data/bin/einhorn ADDED
@@ -0,0 +1,284 @@
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,
47
+ Einhorn can open these sockets and pass them to the workers. Program
48
+ arguments of the form
49
+
50
+ srv:(IP:PORT)[<,OPT>...]
51
+ --MY-OPT=srv:(IP:PORT)[<,OPT>...]
52
+
53
+ Will be interpreted as a request to open a server socket bound to
54
+ IP:PORT. The argument will be replaced with `FD` and `---MY-OPT=FD`,
55
+ respectively, where `FD` is the file descriptor number of the socket.
56
+ Valid opts are:
57
+
58
+ r, so_reuseaddr: set SO_REUSEADDR on the server socket
59
+ n, o_nonblock: set O_NONBLOCK on the server socket
60
+
61
+ You can for example run:
62
+
63
+ $ einhorn -m manual -n 4 example/time_server srv:127.0.0.1:2345,r
64
+
65
+ Which will run 4 copies of
66
+
67
+ example/time_server 6
68
+
69
+ Where file descriptor 6 is a server socket bound to `127.0.0.1:2345`
70
+ and with `SO_REUSEADDR` set. It is then your application's job to
71
+ figure out how to `accept()` on this file descriptor.
72
+
73
+ ### Command socket
74
+
75
+ Einhorn opens a UNIX socket to which you can send commands (run
76
+ `help` in `einhornsh` to see what admin commands you can
77
+ run). Einhorn relies on file permissions to ensure that no malicious
78
+ users can gain access. Run with a `-d DIRECTORY` to change the
79
+ directory where the socket will live.
80
+
81
+ ### Seamless upgrades
82
+
83
+ You can cause your code to be seamlessly reloaded by upgrading the
84
+ worker code on disk and running
85
+
86
+ $ einhornsh
87
+ ...
88
+ > upgrade
89
+
90
+ Once the new workers have been spawned, Einhorn will send each old
91
+ worker a SIGUSR2. SIGUSR2 should be interpreted as a request for a
92
+ graceful shutdown.
93
+
94
+ ### ACKs
95
+
96
+ After Einhorn spawns a worker, it will only consider the worker up
97
+ once it has received an ACK. Currently two ACK mechanisms are
98
+ supported: manual and timer.
99
+
100
+ #### Manual ACK
101
+
102
+ A manual ACK (configured by providing a `-m manual`) requires your
103
+ application to send a command to the command socket once it's
104
+ ready. This is the safest ACK mechanism. If you're writing in Ruby,
105
+ just do
106
+
107
+ require 'einhorn/worker'
108
+ Einhorn::Worker.ack!
109
+
110
+ in your worker code. If you're writing in a different language, or
111
+ don't want to include Einhorn in your namespace, you can send the
112
+ string
113
+
114
+ {"command":"worker:ack", "pid":PID}
115
+
116
+ to the UNIX socket pointed to by the environment variable
117
+ `EINHORN_SOCK_PATH`. (Be sure to include a trailing newline.)
118
+
119
+ To make things even easier, you can pass a `-b` to Einhorn, in which
120
+ case you just need to `write()` the above message to the open file
121
+ descriptor pointed to by `EINHORN_FD`.
122
+
123
+ (See `lib/einhorn/worker.rb` for details of these and other socket
124
+ discovery mechanisms.)
125
+
126
+ #### Timer ACK [default]
127
+
128
+ By default, Einhorn will use a timer ACK of 1 second. That means that
129
+ if your process hasn't exited after 1 second, it is considered ACK'd
130
+ and healthy. You can modify this timeout to be more appropriate for
131
+ your application (and even set to 0 if desired). Just pass a `-m
132
+ FLOAT`.
133
+
134
+ ### Preloading
135
+
136
+ If you're running a Ruby process, Einhorn can optionally preload its
137
+ code, so it only has to load the code once per upgrade rather than
138
+ once per worker process. This also saves on memory overhead, since all
139
+ of the code in these processes will be stored only once using your
140
+ operating system's copy-on-write features.
141
+
142
+ To use preloading, just give Einhorn a `-p PATH_TO_CODE`, and make
143
+ sure you've defined an `einhorn_main` method.
144
+
145
+ In order to maximize compatibility, we've worked to minimize Einhorn's
146
+ dependencies. At the moment Einhorn imports 'rubygems' and 'json'. (If
147
+ these turn out to be issues, we could probably find a workaround.)
148
+
149
+ ### Command name
150
+
151
+ You can set the name that Einhorn and your workers show in PS. Just
152
+ pass `-c <name>`.
153
+ EOF
154
+ end
155
+
156
+ usage << <<EOF
157
+
158
+ ### Options
159
+
160
+ EOF
161
+ end
162
+ end
163
+ end
164
+
165
+ # Would be nice if this could be loadable rather than always
166
+ # executing, but when run under gem it's a bit hard to do so.
167
+ if true # $0 == __FILE__
168
+ Einhorn::TransientState.script_name = $0
169
+ Einhorn::TransientState.argv = ARGV.dup
170
+
171
+ optparse = OptionParser.new do |opts|
172
+ opts.on('-b', '--command-socket-as-fd', 'Leave the command socket open as a file descriptor, passed in the EINHORN_FD environment variable. This allows your worker processes to ACK without needing to know where on the filesystem the command socket lives.') do
173
+ Einhorn::State.command_socket_as_fd = true
174
+ end
175
+
176
+ opts.on('-c CMD_NAME', '--command-name CMD_NAME', 'Set the command name in ps to this value') do |cmd_name|
177
+ Einhorn::TransientState.stateful = false
178
+ Einhorn::State.cmd_name = cmd_name
179
+ end
180
+
181
+ opts.on('-d PATH', '--socket-path PATH', 'Where to open the Einhorn command socket') do |path|
182
+ Einhorn::State.socket_path = path
183
+ end
184
+
185
+ opts.on('-e PIDFILE', '--pidfile PIDFILE', 'Where to write out the Einhorn pidfile') do |pidfile|
186
+ Einhorn::State.pidfile = pidfile
187
+ end
188
+
189
+ opts.on('-f LOCKFILE', '--lockfile LOCKFILE', 'Where to store the Einhorn lockfile') do |lockfile|
190
+ Einhorn::State.lockfile = lockfile
191
+ end
192
+
193
+ opts.on('-h', '--help', 'Display this message') do
194
+ opts.banner = Einhorn::Executable.einhorn_usage(true)
195
+ puts opts
196
+ exit(1)
197
+ end
198
+
199
+ opts.on('-k', '--kill-children-on-exit', 'If Einhorn exits unexpectedly, gracefully kill all its children') do
200
+ Einhorn::State.kill_children_on_exit = true
201
+ end
202
+
203
+ opts.on('-l', '--backlog N', 'Connection backlog (assuming this is a server)') do |b|
204
+ raise "Cannot pass options if stateful" if Einhorn::TransientState.stateful
205
+ Einhorn::TransientState.stateful = false
206
+ Einhorn::State.config[:backlog] = b.to_i
207
+ end
208
+
209
+ 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|
210
+ # Try manual
211
+ if mode == 'manual'
212
+ Einhorn::State.ack_mode = {:type => :manual}
213
+ next
214
+ end
215
+
216
+ # Try float
217
+ begin
218
+ parsed = Float(mode)
219
+ rescue ArgumentError
220
+ else
221
+ Einhorn::State.ack_mode = {:type => :timer, :timeout => parsed}
222
+ next
223
+ end
224
+
225
+ # Give up
226
+ raise "Invalid ack-mode #{mode.inspect} (valid modes: FLOAT or manual)"
227
+ end
228
+
229
+ opts.on('-n', '--number N', 'Number of copies to spin up') do |n|
230
+ raise "Cannot pass options if stateful" if Einhorn::TransientState.stateful
231
+ Einhorn::TransientState.stateful = false
232
+ Einhorn::State.config[:number] = n.to_i
233
+ end
234
+
235
+ 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|
236
+ Einhorn::TransientState.stateful = false
237
+ Einhorn::State.path = path
238
+ end
239
+
240
+ opts.on('-q', '--quiet', 'Make output quiet (can be reconfigured on the fly)') do
241
+ Einhorn::Command.louder(false)
242
+ end
243
+
244
+ opts.on('-s', '--seconds N', 'Number of seconds to wait until respawning') do |b|
245
+ raise "Cannot pass options if stateful" if Einhorn::TransientState.stateful
246
+ Einhorn::TransientState.stateful = false
247
+ Einhorn::State.config[:seconds] = s.to_i
248
+ end
249
+
250
+ opts.on('-v', '--verbose', 'Make output verbose (can be reconfigured on the fly)') do
251
+ Einhorn::Command.louder(false)
252
+ end
253
+
254
+ opts.on('--with-state-fd STATE', '[Internal option] With file descriptor containing state') do |fd|
255
+ raise "Cannot be stateful if options are passed" unless Einhorn::TransientState.stateful.nil?
256
+ Einhorn::TransientState.stateful = true
257
+
258
+ read = IO.for_fd(Integer(fd))
259
+ state = read.read
260
+ read.close
261
+
262
+ Einhorn.restore_state(state)
263
+ end
264
+
265
+ opts.on('--version', 'Show version') do
266
+ puts Einhorn::VERSION
267
+ exit
268
+ end
269
+ end
270
+ optparse.order!
271
+
272
+ if ARGV.length < 1
273
+ optparse.banner = Einhorn::Executable.einhorn_usage(false)
274
+ puts optparse
275
+ exit(1)
276
+ end
277
+
278
+ ret = Einhorn.run
279
+ begin
280
+ exit(ret)
281
+ rescue TypeError
282
+ exit(0)
283
+ end
284
+ end