einhorn 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![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
|