einhorn 0.7.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/Changes.md +10 -0
  3. data/README.md +36 -30
  4. data/bin/einhorn +17 -2
  5. data/einhorn.gemspec +23 -21
  6. data/example/pool_worker.rb +1 -1
  7. data/example/thin_example +8 -8
  8. data/example/time_server +5 -5
  9. data/lib/einhorn/client.rb +8 -9
  10. data/lib/einhorn/command/interface.rb +100 -95
  11. data/lib/einhorn/command.rb +167 -88
  12. data/lib/einhorn/compat.rb +7 -7
  13. data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
  14. data/lib/einhorn/event/ack_timer.rb +2 -2
  15. data/lib/einhorn/event/command_server.rb +7 -9
  16. data/lib/einhorn/event/connection.rb +1 -3
  17. data/lib/einhorn/event/loop_breaker.rb +2 -1
  18. data/lib/einhorn/event/persistent.rb +2 -2
  19. data/lib/einhorn/event/timer.rb +4 -4
  20. data/lib/einhorn/event.rb +29 -20
  21. data/lib/einhorn/prctl.rb +26 -0
  22. data/lib/einhorn/prctl_linux.rb +48 -0
  23. data/lib/einhorn/safe_yaml.rb +17 -0
  24. data/lib/einhorn/version.rb +1 -1
  25. data/lib/einhorn/worker.rb +67 -49
  26. data/lib/einhorn/worker_pool.rb +9 -9
  27. data/lib/einhorn.rb +155 -126
  28. metadata +42 -137
  29. data/.gitignore +0 -17
  30. data/.travis.yml +0 -10
  31. data/CONTRIBUTORS +0 -6
  32. data/Gemfile +0 -11
  33. data/History.txt +0 -4
  34. data/README.md.in +0 -76
  35. data/Rakefile +0 -27
  36. data/test/_lib.rb +0 -12
  37. data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
  38. data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -22
  39. data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
  40. data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -22
  41. data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -143
  42. data/test/integration/_lib/helpers.rb +0 -4
  43. data/test/integration/_lib.rb +0 -6
  44. data/test/integration/startup.rb +0 -31
  45. data/test/integration/upgrading.rb +0 -157
  46. data/test/unit/einhorn/client.rb +0 -88
  47. data/test/unit/einhorn/command/interface.rb +0 -49
  48. data/test/unit/einhorn/command.rb +0 -21
  49. data/test/unit/einhorn/event.rb +0 -89
  50. data/test/unit/einhorn/worker_pool.rb +0 -39
  51. data/test/unit/einhorn.rb +0 -58
  52. /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
- 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.
2
+ # Einhorn: the language-independent shared socket manager
17
3
 
18
- Enter Einhorn. Einhorn makes it easy to run (and keep alive) multiple
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.0, 2.1, and 2.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 is a project of [Stripe](https://stripe.com), led by [Carl Jackson](https://github.com/zenazn). Feel free to get in touch at
257
- info@stripe.com.
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 |b|
270
- Einhorn::State.config[:seconds] = s.to_i
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
- # -*- encoding: utf-8 -*-
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 = ['Greg Brockman']
6
- gem.email = ['gdb@stripe.com']
7
- gem.summary = 'Einhorn: the language-independent shared socket manager'
8
- 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.'
9
- gem.homepage = 'https://github.com/stripe/einhorn'
10
- gem.license = 'MIT'
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 = `git ls-files`.split($\)
13
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
- gem.name = 'einhorn'
16
- gem.require_paths = ['lib']
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.add_development_dependency 'rack', '~> 1.6'
19
- gem.add_development_dependency 'rake'
20
- gem.add_development_dependency 'pry'
21
- gem.add_development_dependency 'minitest', '< 5.0'
22
- gem.add_development_dependency 'mocha', '~> 0.13'
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.version = Einhorn::VERSION
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
@@ -12,7 +12,7 @@
12
12
  puts "From PID #{$$}: loading #{__FILE__}"
13
13
 
14
14
  def einhorn_main
15
- while true
15
+ loop do
16
16
  puts "From PID #{$$}: Doing some work"
17
17
  sleep 1
18
18
  end
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 'rubygems'
10
- require 'einhorn'
9
+ require "rubygems"
10
+ require "einhorn"
11
11
 
12
- require 'eventmachine-le'
13
- require 'thin'
14
- require 'thin/attach_socket'
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
- return [200, {}, "[#{$$}] From server instance #{@id}: Got your request!\n"]
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
- :backend => Thin::Backends::AttachSocket,
45
- :socket => IO.for_fd(sock))
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 'rubygems'
16
- require 'einhorn/worker'
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['EINHORN_FD_0']}"
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
- while true
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 Exception
57
+ rescue Interrupt
58
58
  end
59
59
  end
60
60
 
@@ -1,13 +1,12 @@
1
- require 'set'
2
- require 'uri'
3
- require 'yaml'
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 = URI.escape(serialized, "%\n")
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 = URI.unescape(line)
31
- YAML.load(serialized)
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
- self.new(socket)
36
+ new(socket)
38
37
  end
39
38
 
40
39
  def self.for_fd(fileno)
41
40
  socket = UNIXSocket.for_fd(fileno)
42
- self.new(socket)
41
+ new(socket)
43
42
  end
44
43
 
45
44
  def initialize(socket)