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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +236 -0
- data/README.md.in +79 -0
- data/Rakefile +19 -0
- data/bin/einhorn +284 -0
- data/bin/einhornsh +120 -0
- data/einhorn.gemspec +21 -0
- data/example/pool_worker.rb +19 -0
- data/example/thin_example +52 -0
- data/example/time_server +48 -0
- data/lib/einhorn/client.rb +48 -0
- data/lib/einhorn/command/interface.rb +336 -0
- data/lib/einhorn/command.rb +336 -0
- data/lib/einhorn/event/abstract_text_descriptor.rb +132 -0
- data/lib/einhorn/event/ack_timer.rb +20 -0
- data/lib/einhorn/event/command_server.rb +58 -0
- data/lib/einhorn/event/connection.rb +45 -0
- data/lib/einhorn/event/loop_breaker.rb +6 -0
- data/lib/einhorn/event/persistent.rb +23 -0
- data/lib/einhorn/event/timer.rb +39 -0
- data/lib/einhorn/event.rb +150 -0
- data/lib/einhorn/version.rb +3 -0
- data/lib/einhorn/worker.rb +94 -0
- data/lib/einhorn/worker_pool.rb +56 -0
- data/lib/einhorn.rb +282 -0
- data/test/test_helper.rb +7 -0
- data/test/unit/einhorn/command/interface.rb +47 -0
- data/test/unit/einhorn/command.rb +21 -0
- data/test/unit/einhorn/event.rb +89 -0
- data/test/unit/einhorn.rb +38 -0
- metadata +143 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
+

|
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
|
+

|
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
|