einhorn 0.8.2 → 1.0.1

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +15 -0
  3. data/README.md +7 -38
  4. data/einhorn.gemspec +23 -21
  5. data/example/pool_worker.rb +2 -2
  6. data/example/thin_example +8 -8
  7. data/example/time_server +5 -5
  8. data/lib/einhorn/client.rb +8 -8
  9. data/lib/einhorn/command/interface.rb +92 -98
  10. data/lib/einhorn/command.rb +75 -85
  11. data/lib/einhorn/compat.rb +7 -7
  12. data/lib/einhorn/event/abstract_text_descriptor.rb +32 -36
  13. data/lib/einhorn/event/ack_timer.rb +2 -2
  14. data/lib/einhorn/event/command_server.rb +7 -9
  15. data/lib/einhorn/event/connection.rb +1 -3
  16. data/lib/einhorn/event/loop_breaker.rb +2 -1
  17. data/lib/einhorn/event/persistent.rb +2 -2
  18. data/lib/einhorn/event/timer.rb +4 -4
  19. data/lib/einhorn/event.rb +20 -20
  20. data/lib/einhorn/prctl.rb +2 -2
  21. data/lib/einhorn/prctl_linux.rb +13 -14
  22. data/lib/einhorn/safe_yaml.rb +17 -0
  23. data/lib/einhorn/version.rb +1 -1
  24. data/lib/einhorn/worker.rb +26 -30
  25. data/lib/einhorn/worker_pool.rb +9 -9
  26. data/lib/einhorn.rb +120 -125
  27. metadata +37 -110
  28. data/.gitignore +0 -17
  29. data/.travis.yml +0 -10
  30. data/CONTRIBUTORS +0 -6
  31. data/Gemfile +0 -11
  32. data/History.txt +0 -4
  33. data/README.md.in +0 -94
  34. data/Rakefile +0 -27
  35. data/test/_lib.rb +0 -12
  36. data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
  37. data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -23
  38. data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
  39. data/test/integration/_lib/fixtures/pdeathsig_printer/pdeathsig_printer.rb +0 -29
  40. data/test/integration/_lib/fixtures/signal_timeout/sleepy_server.rb +0 -23
  41. data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -24
  42. data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -148
  43. data/test/integration/_lib/helpers.rb +0 -4
  44. data/test/integration/_lib.rb +0 -6
  45. data/test/integration/pdeathsig.rb +0 -26
  46. data/test/integration/startup.rb +0 -31
  47. data/test/integration/upgrading.rb +0 -204
  48. data/test/unit/_lib/bad_worker.rb +0 -7
  49. data/test/unit/_lib/sleep_worker.rb +0 -5
  50. data/test/unit/einhorn/client.rb +0 -88
  51. data/test/unit/einhorn/command/interface.rb +0 -49
  52. data/test/unit/einhorn/command.rb +0 -135
  53. data/test/unit/einhorn/event.rb +0 -89
  54. data/test/unit/einhorn/worker_pool.rb +0 -39
  55. data/test/unit/einhorn.rb +0 -96
  56. /data/{LICENSE → LICENSE.txt} +0 -0
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0.0
4
- - 2.1
5
- - 2.2
6
-
7
- # This is to work around the version of bundler installed in Travis and
8
- # https://github.com/bundler/bundler/issues/3558
9
- before_install:
10
- - gem update bundler
data/CONTRIBUTORS DELETED
@@ -1,6 +0,0 @@
1
- Greg Brockman <gdb@stripe.com>
2
- Evan Broder <evan@stripe.com>
3
- Nelson Elhage <nelhage@stripe.com>
4
- Paul Hammond <paul@paulhammond.org>
5
- Conrad Irwin <conrad.irwin@gmail.com>
6
- Max Wang <max@stripe.com>
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- # Execute bundler hook if present
2
- ['~/.', '/etc/'].any? do |file|
3
- File.lstat(path = File.expand_path(file + 'bundle-gemfile-hook')) rescue next
4
- eval(File.read(path), binding, path); break true
5
- end || source('https://rubygems.org/')
6
-
7
- # Only needed for examples
8
- gem 'thin-attach_socket'
9
-
10
- # Specify your gem's dependencies in einhorn.gemspec
11
- gemspec
data/History.txt DELETED
@@ -1,4 +0,0 @@
1
- === 0.5.0 2013-07-11
2
-
3
- * 1 major enhancement:
4
- * Improve Einhorn protocol to allow multiplexing of commands on a single socket
data/README.md.in DELETED
@@ -1,94 +0,0 @@
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. You can use Conrad Irwin's thin-attach_socket gem along with
65
- EventMachine-LE to support file-descriptor passing. Check out
66
- `example/thin_example` for an example of running Thin under Einhorn.
67
-
68
- ## Compatibility
69
-
70
- Einhorn runs in Ruby 2.0, 2.1, and 2.2
71
-
72
- The following libraries ease integration with Einhorn with languages other than
73
- Ruby:
74
-
75
- - **[go-einhorn](https://github.com/stripe/go-einhorn)**: Stripe's own library
76
- for *talking* to an einhorn master (doesn't wrap socket code).
77
- - **[goji](https://github.com/zenazn/goji/)**: Go (golang) server framework. The
78
- [`bind`](https://godoc.org/github.com/zenazn/goji/bind) and
79
- [`graceful`](https://godoc.org/github.com/zenazn/goji/graceful)
80
- packages provide helpers and HTTP/TCP connection wrappers for Einhorn
81
- integration.
82
- - **[github.com/CHH/einhorn](https://github.com/CHH/einhorn)**: PHP library
83
- - **[thin-attach\_socket](https://github.com/ConradIrwin/thin-attach_socket)**:
84
- run `thin` behind Einhorn
85
- - **[baseplate](https://reddit.github.io/baseplate/cli/serve.html)**: a
86
- collection of Python helpers and libraries, with support for running behind
87
- Einhorn
88
-
89
- *NB: this list should not imply any official endorsement or vetting!*
90
-
91
- ## About
92
-
93
- Einhorn is a project of [Stripe](https://stripe.com), led by [Carl Jackson](https://github.com/zenazn). Feel free to get in touch at
94
- info@stripe.com.
data/Rakefile DELETED
@@ -1,27 +0,0 @@
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
- task :default => :test do
15
- end
16
- require 'bundler/setup'
17
- require 'chalk-rake/gem_tasks'
18
- require 'rake/testtask'
19
-
20
- Rake::TestTask.new do |t|
21
- t.libs = ['lib']
22
- # t.warning = true
23
- t.verbose = true
24
- t.test_files = FileList['test/**/*.rb'].reject do |file|
25
- file.end_with?('_lib.rb') || file.include?('/_lib/')
26
- end
27
- end
data/test/_lib.rb DELETED
@@ -1,12 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
-
4
- require 'minitest/autorun'
5
- require 'minitest/spec'
6
- require 'mocha/setup'
7
-
8
- class EinhornTestCase < ::MiniTest::Spec
9
- def setup
10
- # Put global stubs here
11
- end
12
- end
@@ -1,26 +0,0 @@
1
- require 'bundler/setup'
2
- require 'socket'
3
- require 'einhorn/worker'
4
-
5
- def einhorn_main
6
- $stderr.puts "Worker starting up!"
7
- serv = Socket.for_fd(ENV['EINHORN_FD_0'].to_i)
8
- $stderr.puts "Worker has a socket"
9
- Einhorn::Worker.ack!
10
- $stderr.puts "Worker sent ack to einhorn"
11
- $stdout.puts "Environment from #{Process.pid} is: #{ENV.inspect}"
12
- while true
13
- s, addrinfo = serv.accept
14
- $stderr.puts "Worker got a socket!"
15
- output = ""
16
- ARGV.each do |variable_to_write|
17
- output += ENV[variable_to_write].to_s
18
- end
19
- s.write(output)
20
- s.flush
21
- s.close
22
- $stderr.puts "Worker closed its socket"
23
- end
24
- end
25
-
26
- einhorn_main if $0 == __FILE__
@@ -1,23 +0,0 @@
1
- require 'bundler/setup'
2
- require 'socket'
3
- require 'einhorn/worker'
4
-
5
- def einhorn_main
6
- serv = Socket.for_fd(Einhorn::Worker.socket!)
7
- Einhorn::Worker.ack!
8
- Einhorn::Worker.ping!("id-1")
9
-
10
- Signal.trap('USR2') do
11
- sleep 3
12
- exit!
13
- end
14
-
15
- while true
16
- s, _ = serv.accept
17
- s.write($$)
18
- s.flush
19
- s.close
20
- end
21
- end
22
-
23
- einhorn_main if $0 == __FILE__
@@ -1,6 +0,0 @@
1
- #!/bin/sh
2
-
3
- if [ "$1" = "--with-state-fd" ]; then
4
- sleep 6
5
- fi
6
- exec bundle exec --keep-file-descriptors einhorn "$@"
@@ -1,29 +0,0 @@
1
- require 'bundler/setup'
2
- require 'socket'
3
- require 'einhorn/worker'
4
- require 'einhorn/prctl'
5
-
6
- def einhorn_main
7
- serv = Socket.for_fd(Einhorn::Worker.socket!)
8
-
9
- Signal.trap("USR2") { exit }
10
-
11
- begin
12
- output = Einhorn::Prctl.get_pdeathsig
13
- if output == nil then
14
- output = "nil"
15
- end
16
- rescue NotImplementedError
17
- output = "not implemented"
18
- end
19
-
20
- Einhorn::Worker.ack!
21
- while true
22
- s, _ = serv.accept
23
- s.write(output)
24
- s.flush
25
- s.close
26
- end
27
- end
28
-
29
- einhorn_main if $0 == __FILE__
@@ -1,23 +0,0 @@
1
- require 'bundler/setup'
2
- require 'socket'
3
- require 'einhorn/worker'
4
-
5
- def einhorn_main
6
- serv = Socket.for_fd(Einhorn::Worker.socket!)
7
- Einhorn::Worker.ack!
8
- Einhorn::Worker.ping!("id-1")
9
-
10
- Signal.trap('USR2') do
11
- sleep ENV.fetch("TRAP_SLEEP").to_i
12
- exit
13
- end
14
-
15
- while true
16
- s, _ = serv.accept
17
- s.write($$)
18
- s.flush
19
- s.close
20
- end
21
- end
22
-
23
- einhorn_main if $0 == __FILE__
@@ -1,24 +0,0 @@
1
- require 'bundler/setup'
2
- require 'socket'
3
- require 'einhorn/worker'
4
-
5
- def einhorn_main
6
- version = File.read(File.join(File.dirname(__FILE__), "version"))
7
- $stderr.puts "Worker starting up!"
8
- serv = Socket.for_fd(ENV['EINHORN_FD_0'].to_i)
9
- $stderr.puts "Worker has a socket"
10
- Einhorn::Worker.ack!
11
- $stderr.puts "Worker sent ack to einhorn"
12
- Einhorn::Worker.ping!("id-1")
13
- $stderr.puts "Worker has sent a ping to einhorn"
14
- while true
15
- s, addrinfo = serv.accept
16
- $stderr.puts "Worker got a socket!"
17
- s.write(version)
18
- s.flush
19
- s.close
20
- $stderr.puts "Worker closed its socket"
21
- end
22
- end
23
-
24
- einhorn_main if $0 == __FILE__
@@ -1,148 +0,0 @@
1
- require 'subprocess'
2
- require 'timeout'
3
- require 'tmpdir'
4
-
5
- module Helpers
6
- module EinhornHelpers
7
- def einhorn_code_dir
8
- File.expand_path('../../../../', File.dirname(__FILE__))
9
- end
10
-
11
- def default_einhorn_command
12
- cmd = ['bundle', 'exec']
13
- cmd << '--keep-file-descriptors' if RUBY_VERSION >= '2.0'
14
- cmd << File.expand_path('bin/einhorn', einhorn_code_dir)
15
-
16
- cmd
17
- end
18
-
19
- def with_running_einhorn(cmdline, options = {})
20
- options = options.dup
21
- einhorn_command = options.delete(:einhorn_command) { default_einhorn_command }
22
- expected_exit_code = options.delete(:expected_exit_code) { nil }
23
- output_callback = options.delete(:output_callback) { nil }
24
-
25
- stdout, stderr = "", ""
26
- communicator = nil
27
- process = Bundler.with_clean_env do
28
- default_options = {
29
- :stdout => Subprocess::PIPE,
30
- :stderr => Subprocess::PIPE,
31
- :stdin => '/dev/null',
32
- :cwd => einhorn_code_dir
33
- }
34
- Subprocess::Process.new(Array(einhorn_command) + cmdline, default_options.merge(options))
35
- end
36
-
37
- status = nil
38
- begin
39
- communicator = Thread.new do
40
- begin
41
- stdout, stderr = process.communicate
42
- rescue Errno::ECHILD
43
- # It's dead, and we're not getting anything. This is peaceful.
44
- end
45
- end
46
- yield(process) if block_given?
47
- rescue
48
- unless (status = process.poll) && status.exited?
49
- process.terminate
50
- end
51
- raise
52
- ensure
53
- unless (status = process.poll) && status.exited?
54
- for i in 1..10 do
55
- status = process.poll
56
- if status && status.exited?
57
- break
58
- end
59
- sleep(1)
60
- end
61
- unless status && status.exited?
62
- $stderr.puts "Could not get Einhorn to quit within 10 seconds, killing it forcefully..."
63
- process.send_signal("KILL")
64
- status = process.wait
65
- end
66
- end
67
- communicator.join
68
- output_callback.call(stdout, stderr) if output_callback
69
- end
70
- end
71
-
72
- def einhornsh(commandline, options = {})
73
- Subprocess.check_call(%W{bundle exec #{File.expand_path('bin/einhornsh')}} + commandline,
74
- {
75
- :stdin => '/dev/null',
76
- :stdout => '/dev/null',
77
- :stderr => '/dev/null'
78
- }.merge(options))
79
- end
80
-
81
- def fixture_path(name)
82
- File.expand_path(File.join('../fixtures', name), File.dirname(__FILE__))
83
- end
84
-
85
- # Creates a new temporary directory with the initial contents from
86
- # test/integration/_lib/fixtures/{name} and returns the path to
87
- # it. The contents of this directory are temporary and can be
88
- # safely overwritten.
89
- def prepare_fixture_directory(name)
90
- @fixtured_dirs ||= Set.new
91
- new_dir = Dir.mktmpdir(name)
92
- @fixtured_dirs << new_dir
93
- FileUtils.cp_r(File.join(fixture_path(name), '.'), new_dir, :preserve => true)
94
-
95
- new_dir
96
- end
97
-
98
- def cleanup_fixtured_directories
99
- (@fixtured_dirs || []).each { |dir| FileUtils.rm_rf(dir) }
100
- end
101
-
102
- def find_free_port(host='127.0.0.1')
103
- open_port = TCPServer.new(host, 0)
104
- open_port.addr[1]
105
- ensure
106
- open_port.close
107
- end
108
-
109
- def get_state(client)
110
- client.send_command('command' => 'state')
111
- YAML.load(client.receive_message['message'])[:state]
112
- end
113
-
114
- def wait_for_open_port
115
- max_retries = 50
116
- begin
117
- read_from_port
118
- rescue Errno::ECONNREFUSED
119
- max_retries -= 1
120
- if max_retries <= 0
121
- raise
122
- else
123
- sleep 0.1
124
- retry
125
- end
126
- end
127
- end
128
-
129
-
130
- def read_from_port
131
- ewouldblock = RUBY_VERSION >= '1.9.0' ? IO::WaitWritable : Errno::EINPROGRESS
132
- socket = Socket.new(Socket::PF_INET, Socket::SOCK_STREAM, 0)
133
- sockaddr = Socket.pack_sockaddr_in(@port, '127.0.0.1')
134
- begin
135
- socket.connect_nonblock(sockaddr)
136
- rescue ewouldblock
137
- IO.select(nil, [socket], [], 5)
138
- begin
139
- socket.connect_nonblock(sockaddr)
140
- rescue Errno::EISCONN
141
- end
142
- end
143
- socket.read.chomp
144
- ensure
145
- socket.close if socket
146
- end
147
- end
148
- end
@@ -1,4 +0,0 @@
1
- module Helpers
2
- end
3
-
4
- require(File.expand_path('helpers/einhorn_helpers', File.dirname(__FILE__)))
@@ -1,6 +0,0 @@
1
- require(File.expand_path('../_lib', File.dirname(__FILE__)))
2
- require(File.expand_path('_lib/helpers', File.dirname(__FILE__)))
3
-
4
- class EinhornIntegrationTestCase < EinhornTestCase
5
-
6
- end
@@ -1,26 +0,0 @@
1
- require(File.expand_path('_lib', File.dirname(__FILE__)))
2
-
3
- class PdeathsigTest < EinhornIntegrationTestCase
4
- include Helpers::EinhornHelpers
5
-
6
- describe 'when run with -k' do
7
- before do
8
- @dir = prepare_fixture_directory('pdeathsig_printer')
9
- @port = find_free_port
10
- @server_program = File.join(@dir, 'pdeathsig_printer.rb')
11
- @socket_path = File.join(@dir, 'einhorn.sock')
12
- end
13
- after { cleanup_fixtured_directories }
14
-
15
- it 'sets pdeathsig to USR2 in the child process' do
16
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} -d #{@socket_path} -k -- ruby #{@server_program}}) do |process|
17
- wait_for_open_port
18
- output = read_from_port
19
- if output != "not implemented" then
20
- assert_equal("USR2", output)
21
- end
22
- process.terminate
23
- end
24
- end
25
- end
26
- end
@@ -1,31 +0,0 @@
1
- require(File.expand_path('_lib', File.dirname(__FILE__)))
2
-
3
- class StartupTest < EinhornIntegrationTestCase
4
- include Helpers::EinhornHelpers
5
-
6
- describe 'when invoked without args' do
7
- it 'prints usage and exits with 1' do
8
- assert_raises(Subprocess::NonZeroExit) do
9
- Subprocess.check_call(default_einhorn_command,
10
- :stdout => Subprocess::PIPE,
11
- :stderr => Subprocess::PIPE) do |einhorn|
12
- stdout, stderr = einhorn.communicate
13
- assert_match(/\A## Usage/, stdout)
14
- assert_equal(1, einhorn.wait.exitstatus)
15
- end
16
- end
17
- end
18
- end
19
-
20
- describe 'when invoked with --upgrade-check' do
21
- it 'successfully exits' do
22
- Subprocess.check_call(default_einhorn_command + %w[--upgrade-check],
23
- :stdout => Subprocess::PIPE,
24
- :stderr => Subprocess::PIPE) do |einhorn|
25
- stdout, stderr = einhorn.communicate
26
- status = einhorn.wait
27
- assert_equal(0, status.exitstatus)
28
- end
29
- end
30
- end
31
- end