einhorn 0.7.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changes.md +10 -0
- data/README.md +36 -30
- data/bin/einhorn +17 -2
- data/einhorn.gemspec +23 -21
- data/example/pool_worker.rb +1 -1
- data/example/thin_example +8 -8
- data/example/time_server +5 -5
- data/lib/einhorn/client.rb +8 -9
- data/lib/einhorn/command/interface.rb +100 -95
- data/lib/einhorn/command.rb +167 -88
- data/lib/einhorn/compat.rb +7 -7
- data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
- data/lib/einhorn/event/ack_timer.rb +2 -2
- data/lib/einhorn/event/command_server.rb +7 -9
- data/lib/einhorn/event/connection.rb +1 -3
- data/lib/einhorn/event/loop_breaker.rb +2 -1
- data/lib/einhorn/event/persistent.rb +2 -2
- data/lib/einhorn/event/timer.rb +4 -4
- data/lib/einhorn/event.rb +29 -20
- data/lib/einhorn/prctl.rb +26 -0
- data/lib/einhorn/prctl_linux.rb +48 -0
- data/lib/einhorn/safe_yaml.rb +17 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +67 -49
- data/lib/einhorn/worker_pool.rb +9 -9
- data/lib/einhorn.rb +155 -126
- metadata +42 -137
- data/.gitignore +0 -17
- data/.travis.yml +0 -10
- data/CONTRIBUTORS +0 -6
- data/Gemfile +0 -11
- data/History.txt +0 -4
- data/README.md.in +0 -76
- data/Rakefile +0 -27
- data/test/_lib.rb +0 -12
- data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
- data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -22
- data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
- data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -22
- data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -143
- data/test/integration/_lib/helpers.rb +0 -4
- data/test/integration/_lib.rb +0 -6
- data/test/integration/startup.rb +0 -31
- data/test/integration/upgrading.rb +0 -157
- data/test/unit/einhorn/client.rb +0 -88
- data/test/unit/einhorn/command/interface.rb +0 -49
- data/test/unit/einhorn/command.rb +0 -21
- data/test/unit/einhorn/event.rb +0 -89
- data/test/unit/einhorn/worker_pool.rb +0 -39
- data/test/unit/einhorn.rb +0 -58
- /data/{LICENSE → LICENSE.txt} +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d96567bd331e59f404e30305612c693fd064558128f41dd6ebad15e0f5bf037f
|
4
|
+
data.tar.gz: a022f5e47a3aa1754d7cc8913975dab3269fc8aa8e7d1a7af83ae7d064f2c97a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83551dfa88c86a66ed8ba3242c193a6ae6f8e43e780642a346fa2ad54840ea0fe7cad8cc215a416cb97128861e7e3ffd88481a40b4fe77309153a5947265d59c
|
7
|
+
data.tar.gz: fede975c4e07752839033e9752bbc22a7b143400f726fd5d330cd8c0d2fadf84c182c00dc43ad0ee105452e1a537157168bf62595e9092d9afb6290d86bebd97
|
data/Changes.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# 1.0.0
|
2
|
+
|
3
|
+
- Use `YAML.safe_load` for compatibility with Ruby 3.1+ [#102]
|
4
|
+
- Functionality previously commented or logged as deprecated has been removed.
|
5
|
+
- Standardize code formatting with `standard`
|
6
|
+
- Add GitHub CI with Ruby version matrix
|
7
|
+
- Drop support for Rubies below 2.5.
|
8
|
+
|
9
|
+
Einhorn is now owned and actively maintained by Mike Perham of Contributed Systems.
|
10
|
+
Thank you to the Stripe developers who wrote Einhorn and maintained it to this point.
|
data/README.md
CHANGED
@@ -1,21 +1,7 @@
|
|
1
|
-
# Einhorn: the language-independent shared socket manager
|
2
|
-
|
3
|
-
![Einhorn](https://stripe.com/img/blog/posts/meet-einhorn/einhorn.png)
|
4
1
|
|
5
|
-
|
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.
|
2
|
+
# Einhorn: the language-independent shared socket manager
|
17
3
|
|
18
|
-
|
4
|
+
Einhorn makes it easy to run (and keep alive) multiple
|
19
5
|
copies of a single long-lived process. If that process is a server
|
20
6
|
listening on some socket, Einhorn will open the socket in the master
|
21
7
|
process so that it's shared among the workers.
|
@@ -194,6 +180,17 @@ library.
|
|
194
180
|
You can set the name that Einhorn and your workers show in PS. Just
|
195
181
|
pass `-c <name>`.
|
196
182
|
|
183
|
+
### Re exec
|
184
|
+
|
185
|
+
You can use the `--reexec-as` option to replace the `einhorn` command with a command or script of your own. This might be useful for those with a Capistrano like deploy process that has changing symlinks. To ensure that you are following the symlinks you could use a bash script like this.
|
186
|
+
|
187
|
+
#!/bin/bash
|
188
|
+
|
189
|
+
cd <symlinked directory>
|
190
|
+
exec /usr/local/bin/einhorn "$@"
|
191
|
+
|
192
|
+
Then you could set `--reexec-as=` to the name of your bash script and it will run in place of the plain einhorn command.
|
193
|
+
|
197
194
|
### Options
|
198
195
|
|
199
196
|
-b, --bind ADDR Bind an address and add the corresponding FD via the environment
|
@@ -217,20 +214,10 @@ pass `-c <name>`.
|
|
217
214
|
Unix nice level at which to run the einhorn processes. If not running as root, make sure to ulimit -e as appopriate.
|
218
215
|
--with-state-fd STATE [Internal option] With file descriptor containing state
|
219
216
|
--upgrade-check [Internal option] Check if Einhorn can exec itself and exit with status 0 before loading code
|
217
|
+
-t, --signal-timeout=T If children do not react to signals after T seconds, escalate to SIGKILL
|
220
218
|
--version Show version
|
221
219
|
|
222
220
|
|
223
|
-
## Contributing
|
224
|
-
|
225
|
-
Contributions are definitely welcome. To contribute, just follow the
|
226
|
-
usual workflow:
|
227
|
-
|
228
|
-
1. Fork Einhorn
|
229
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
230
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
231
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
232
|
-
5. Create new Github pull request
|
233
|
-
|
234
221
|
## History
|
235
222
|
|
236
223
|
Einhorn came about when Stripe was investigating seamless code
|
@@ -249,9 +236,28 @@ EventMachine-LE to support file-descriptor passing. Check out
|
|
249
236
|
|
250
237
|
## Compatibility
|
251
238
|
|
252
|
-
Einhorn runs in Ruby 2.
|
239
|
+
Einhorn runs in Ruby 2.5+.
|
240
|
+
|
241
|
+
The following libraries ease integration with Einhorn with languages other than
|
242
|
+
Ruby:
|
243
|
+
|
244
|
+
- **[go-einhorn](https://github.com/stripe/go-einhorn)**: Stripe's own library
|
245
|
+
for *talking* to an einhorn master (doesn't wrap socket code).
|
246
|
+
- **[goji](https://github.com/zenazn/goji/)**: Go (golang) server framework. The
|
247
|
+
[`bind`](https://godoc.org/github.com/zenazn/goji/bind) and
|
248
|
+
[`graceful`](https://godoc.org/github.com/zenazn/goji/graceful)
|
249
|
+
packages provide helpers and HTTP/TCP connection wrappers for Einhorn
|
250
|
+
integration.
|
251
|
+
- **[github.com/CHH/einhorn](https://github.com/CHH/einhorn)**: PHP library
|
252
|
+
- **[thin-attach\_socket](https://github.com/ConradIrwin/thin-attach_socket)**:
|
253
|
+
run `thin` behind Einhorn
|
254
|
+
- **[baseplate](https://reddit.github.io/baseplate/cli/serve.html)**: a
|
255
|
+
collection of Python helpers and libraries, with support for running behind
|
256
|
+
Einhorn
|
257
|
+
|
258
|
+
*NB: this list should not imply any official endorsement or vetting!*
|
253
259
|
|
254
260
|
## About
|
255
261
|
|
256
|
-
Einhorn
|
257
|
-
|
262
|
+
Einhorn was a project of [Stripe](https://stripe.com).
|
263
|
+
It is now maintained by [Contributed Systems](https://contribsys.com).
|
data/bin/einhorn
CHANGED
@@ -266,8 +266,11 @@ if true # $0 == __FILE__
|
|
266
266
|
Einhorn::Command.quieter(false)
|
267
267
|
end
|
268
268
|
|
269
|
-
opts.on('-s', '--seconds N', 'Number of seconds to wait until respawning') do |
|
270
|
-
|
269
|
+
opts.on('-s', '--seconds N', 'Number of seconds to wait until respawning') do |s|
|
270
|
+
seconds = Float(s)
|
271
|
+
raise ArgumentError, 'seconds must be > 0' if seconds.zero?
|
272
|
+
|
273
|
+
Einhorn::State.config[:seconds] = seconds
|
271
274
|
end
|
272
275
|
|
273
276
|
opts.on('-v', '--verbose', 'Make output verbose (can be reconfigured on the fly)') do
|
@@ -310,6 +313,18 @@ if true # $0 == __FILE__
|
|
310
313
|
Einhorn::State.signal_timeout = Integer(t)
|
311
314
|
end
|
312
315
|
|
316
|
+
opts.on('--max-unacked=N', 'Maximum number of workers that can be unacked when gracefully upgrading.') do |n|
|
317
|
+
Einhorn::State.config[:max_unacked] = Integer(n)
|
318
|
+
end
|
319
|
+
|
320
|
+
opts.on('--max-upgrade-additional=N', 'Maximum number of additional workers that can be running during an upgrade.') do |n|
|
321
|
+
Einhorn::State.config[:max_upgrade_additional] = Integer(n)
|
322
|
+
end
|
323
|
+
|
324
|
+
opts.on('--gc-before-fork', 'Run the GC three times before forking to improve memory sharing for copy-on-write.') do
|
325
|
+
Einhorn::State.config[:gc_before_fork] = true
|
326
|
+
end
|
327
|
+
|
313
328
|
opts.on('--version', 'Show version') do
|
314
329
|
puts Einhorn::VERSION
|
315
330
|
exit
|
data/einhorn.gemspec
CHANGED
@@ -1,27 +1,29 @@
|
|
1
|
-
|
2
|
-
require File.expand_path('../lib/einhorn/version', __FILE__)
|
1
|
+
require File.expand_path("../lib/einhorn/version", __FILE__)
|
3
2
|
|
4
3
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors
|
6
|
-
gem.email
|
7
|
-
gem.summary
|
8
|
-
gem.description
|
9
|
-
gem.homepage
|
10
|
-
gem.license
|
4
|
+
gem.authors = ["Stripe", "Mike Perham"]
|
5
|
+
gem.email = ["support+github@stripe.com", "mperham@gmail.com"]
|
6
|
+
gem.summary = "Einhorn: the language-independent shared socket manager"
|
7
|
+
gem.description = "Einhorn makes it easy to run multiple instances of an application server, all listening on the same port. You can also seamlessly restart your workers without dropping any requests. Einhorn requires minimal application-level support, making it easy to use with an existing project."
|
8
|
+
gem.homepage = "https://github.com/contribsys/einhorn"
|
9
|
+
gem.license = "MIT"
|
11
10
|
|
12
|
-
gem.files
|
13
|
-
gem.executables
|
14
|
-
gem.test_files
|
15
|
-
gem.name
|
16
|
-
gem.require_paths = [
|
11
|
+
gem.files = ["einhorn.gemspec", "README.md", "Changes.md", "LICENSE.txt"] + `git ls-files bin lib example`.split("\n")
|
12
|
+
gem.executables = %w[einhorn einhornsh]
|
13
|
+
gem.test_files = []
|
14
|
+
gem.name = "einhorn"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.required_ruby_version = ">= 2.5.0"
|
17
|
+
gem.version = Einhorn::VERSION
|
17
18
|
|
18
|
-
gem.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
gem.add_development_dependency 'chalk-rake'
|
24
|
-
gem.add_development_dependency 'subprocess'
|
19
|
+
gem.metadata = {
|
20
|
+
"bug_tracker_uri" => "https://github.com/contribsys/einhorn/issues",
|
21
|
+
"documentation_uri" => "https://github.com/contribsys/einhorn/wiki",
|
22
|
+
"changelog_uri" => "https://github.com/contribsys/einhorn/blob/main/Changes.md"
|
23
|
+
}
|
25
24
|
|
26
|
-
gem.
|
25
|
+
gem.add_development_dependency "rake", "~> 13"
|
26
|
+
gem.add_development_dependency "minitest", "~> 5"
|
27
|
+
gem.add_development_dependency "mocha", "~> 1"
|
28
|
+
gem.add_development_dependency "subprocess", "~> 1"
|
27
29
|
end
|
data/example/pool_worker.rb
CHANGED
data/example/thin_example
CHANGED
@@ -6,12 +6,12 @@
|
|
6
6
|
# https://github.com/stripe/thin.git, and
|
7
7
|
# https://github.com/stripe/eventmachine.git
|
8
8
|
|
9
|
-
require
|
10
|
-
require
|
9
|
+
require "rubygems"
|
10
|
+
require "einhorn"
|
11
11
|
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
12
|
+
require "eventmachine-le"
|
13
|
+
require "thin"
|
14
|
+
require "thin/attach_socket"
|
15
15
|
|
16
16
|
class App
|
17
17
|
def initialize(id)
|
@@ -19,7 +19,7 @@ class App
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def call(env)
|
22
|
-
|
22
|
+
[200, {}, "[#{$$}] From server instance #{@id}: Got your request!\n"]
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -41,8 +41,8 @@ def einhorn_main
|
|
41
41
|
(0...fd_count).each do |i|
|
42
42
|
sock = Einhorn::Worker.socket!(i)
|
43
43
|
srv = Thin::Server.new(App.new(i),
|
44
|
-
:
|
45
|
-
:
|
44
|
+
backend: Thin::Backends::AttachSocket,
|
45
|
+
socket: IO.for_fd(sock))
|
46
46
|
srv.start
|
47
47
|
end
|
48
48
|
end
|
data/example/time_server
CHANGED
@@ -12,15 +12,15 @@
|
|
12
12
|
# or, if you want to try out preloading:
|
13
13
|
#
|
14
14
|
# einhorn -b 127.0.0.1:2345,r -p ./time_server ./time_server
|
15
|
-
require
|
16
|
-
require
|
15
|
+
require "rubygems"
|
16
|
+
require "einhorn/worker"
|
17
17
|
|
18
18
|
def log(msg)
|
19
19
|
puts "=== [#{$$}] #{msg}"
|
20
20
|
end
|
21
21
|
|
22
22
|
def einhorn_main
|
23
|
-
log "Called with ENV['EINHORN_FD_0']: #{ENV[
|
23
|
+
log "Called with ENV['EINHORN_FD_0']: #{ENV["EINHORN_FD_0"]}"
|
24
24
|
|
25
25
|
fd_num = Einhorn::Worker.socket!
|
26
26
|
socket = Socket.for_fd(fd_num)
|
@@ -49,12 +49,12 @@ def einhorn_main
|
|
49
49
|
|
50
50
|
# Real work happens here.
|
51
51
|
begin
|
52
|
-
|
52
|
+
loop do
|
53
53
|
accepted, _ = socket.accept
|
54
54
|
accepted.write("[#{$$}] The current time is: #{Time.now}!\n")
|
55
55
|
accepted.close
|
56
56
|
end
|
57
|
-
rescue
|
57
|
+
rescue Interrupt
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
data/lib/einhorn/client.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "set"
|
2
|
+
require "yaml"
|
3
|
+
require "einhorn/safe_yaml"
|
4
4
|
|
5
5
|
module Einhorn
|
6
6
|
class Client
|
7
7
|
# Keep this in this file so client can be loaded entirely
|
8
8
|
# standalone by user code.
|
9
9
|
module Transport
|
10
|
-
|
11
10
|
ParseError = defined?(Psych::SyntaxError) ? Psych::SyntaxError : ArgumentError
|
12
11
|
|
13
12
|
def self.send_message(socket, message)
|
@@ -22,24 +21,24 @@ module Einhorn
|
|
22
21
|
|
23
22
|
def self.serialize_message(message)
|
24
23
|
serialized = YAML.dump(message)
|
25
|
-
escaped =
|
24
|
+
escaped = serialized.gsub(/%|\n/, "%" => "%25", "\n" => "%0A")
|
26
25
|
escaped + "\n"
|
27
26
|
end
|
28
27
|
|
29
28
|
def self.deserialize_message(line)
|
30
|
-
serialized =
|
31
|
-
|
29
|
+
serialized = line.gsub(/%(25|0A)/, "%25" => "%", "%0A" => "\n")
|
30
|
+
SafeYAML.load(serialized)
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
34
|
def self.for_path(path_to_socket)
|
36
35
|
socket = UNIXSocket.open(path_to_socket)
|
37
|
-
|
36
|
+
new(socket)
|
38
37
|
end
|
39
38
|
|
40
39
|
def self.for_fd(fileno)
|
41
40
|
socket = UNIXSocket.for_fd(fileno)
|
42
|
-
|
41
|
+
new(socket)
|
43
42
|
end
|
44
43
|
|
45
44
|
def initialize(socket)
|