einhorn 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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