einhorn 0.8.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +10 -0
  3. data/{LICENSE → LICENSE.txt} +0 -0
  4. data/README.md +5 -36
  5. data/einhorn.gemspec +23 -21
  6. data/example/pool_worker.rb +2 -2
  7. data/example/thin_example +8 -8
  8. data/example/time_server +5 -5
  9. data/lib/einhorn/client.rb +8 -8
  10. data/lib/einhorn/command/interface.rb +92 -98
  11. data/lib/einhorn/command.rb +76 -85
  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 +19 -19
  21. data/lib/einhorn/prctl.rb +2 -2
  22. data/lib/einhorn/prctl_linux.rb +13 -14
  23. data/lib/einhorn/safe_yaml.rb +17 -0
  24. data/lib/einhorn/version.rb +1 -1
  25. data/lib/einhorn/worker.rb +26 -30
  26. data/lib/einhorn/worker_pool.rb +9 -9
  27. data/lib/einhorn.rb +120 -125
  28. metadata +33 -117
  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 -94
  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 -23
  39. data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
  40. data/test/integration/_lib/fixtures/pdeathsig_printer/pdeathsig_printer.rb +0 -29
  41. data/test/integration/_lib/fixtures/signal_timeout/sleepy_server.rb +0 -23
  42. data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -24
  43. data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -148
  44. data/test/integration/_lib/helpers.rb +0 -4
  45. data/test/integration/_lib.rb +0 -6
  46. data/test/integration/pdeathsig.rb +0 -26
  47. data/test/integration/startup.rb +0 -31
  48. data/test/integration/upgrading.rb +0 -204
  49. data/test/unit/_lib/bad_worker.rb +0 -7
  50. data/test/unit/_lib/sleep_worker.rb +0 -5
  51. data/test/unit/einhorn/client.rb +0 -88
  52. data/test/unit/einhorn/command/interface.rb +0 -49
  53. data/test/unit/einhorn/command.rb +0 -135
  54. data/test/unit/einhorn/event.rb +0 -89
  55. data/test/unit/einhorn/worker_pool.rb +0 -39
  56. data/test/unit/einhorn.rb +0 -96
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