einhorn 0.7.4 → 1.0.0

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 (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
@@ -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
@@ -1,157 +0,0 @@
1
- require(File.expand_path('_lib', File.dirname(__FILE__)))
2
- require 'socket'
3
- require 'einhorn/client'
4
-
5
- class UpgradeTests < EinhornIntegrationTestCase
6
- include Helpers::EinhornHelpers
7
-
8
- describe 'when upgrading a running einhorn without preloading' do
9
- before do
10
- @dir = prepare_fixture_directory('upgrade_project')
11
- @port = find_free_port
12
- @server_program = File.join(@dir, "upgrading_server.rb")
13
- @socket_path = File.join(@dir, "einhorn.sock")
14
- end
15
- after { cleanup_fixtured_directories }
16
-
17
- it 'can restart' do
18
- File.open(File.join(@dir, "version"), 'w') { |f| f.write("0") }
19
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} -d #{@socket_path} -- ruby #{@server_program}}) do |process|
20
- wait_for_open_port
21
- assert_equal("0", read_from_port, "Should report the initial version")
22
-
23
- File.open(File.join(@dir, "version"), 'w') { |f| f.write("1") }
24
- einhornsh(%W{-d #{@socket_path} -e upgrade})
25
- assert_equal("1", read_from_port, "Should report the upgraded version")
26
-
27
- process.terminate
28
- end
29
- end
30
- end
31
-
32
- describe 'handling environments on upgrade' do
33
- before do
34
- @dir = prepare_fixture_directory('env_printer')
35
- @port = find_free_port
36
- @server_program = File.join(@dir, "env_printer.rb")
37
- @socket_path = File.join(@dir, "einhorn.sock")
38
- end
39
- after { cleanup_fixtured_directories }
40
-
41
- describe 'when running with --reexec-as' do
42
- it 'preserves environment variables across restarts' do
43
- # exec the new einhorn with the same environment:
44
- reexec_cmdline = 'env VAR=a bundle exec --keep-file-descriptors einhorn'
45
-
46
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR},
47
- :env => ENV.to_hash.merge({'VAR' => 'a'})) do |process|
48
-
49
- wait_for_open_port
50
- einhornsh(%W{-d #{@socket_path} -e upgrade})
51
- assert_equal("a", read_from_port, "Should report the upgraded version")
52
-
53
- process.terminate
54
- end
55
- end
56
-
57
- it 'cleans up if a child dies during the reexec' do
58
- # attempt to setup a scenario where a child exits in the
59
- # interlude after old einhorn has execed the reexec-as
60
- # command, but before the reexec-as command execs new einhorn
61
-
62
- @dir = prepare_fixture_directory('exit_during_upgrade')
63
- @server_program = File.join(@dir, "exiting_server.rb")
64
- @socket_path = File.join(@dir, "einhorn.sock")
65
-
66
- reexec_cmdline = File.join(@dir, 'upgrade_reexec.rb')
67
-
68
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program}}) do |process|
69
- wait_for_open_port
70
-
71
- Process.kill('USR2', read_from_port.to_i)
72
- einhornsh(%W{-d #{@socket_path} -e upgrade})
73
-
74
- client = Einhorn::Client.for_path(@socket_path)
75
- client.send_command('command' => 'state')
76
- resp = client.receive_message
77
-
78
- state = YAML.load(resp['message'])
79
- assert_equal(1, state[:state][:children].count)
80
-
81
- process.terminate
82
- end
83
- end
84
-
85
- describe 'without preloading' do
86
- it 'can update environment variables when the reexec command line says to' do
87
- # exec the new einhorn with the same environment:
88
- reexec_cmdline = 'env VAR=b OINK=b bundle exec --keep-file-descriptors einhorn'
89
-
90
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR},
91
- :env => ENV.to_hash.merge({'VAR' => 'a'})) do |process|
92
-
93
- wait_for_open_port
94
- einhornsh(%W{-d #{@socket_path} -e upgrade})
95
- assert_equal("b", read_from_port, "Should report the upgraded version")
96
-
97
- process.terminate
98
- end
99
- end
100
- end
101
-
102
- describe 'with preloading' do
103
- it 'can update environment variables on preloaded code when the reexec command line says to' do
104
- # exec the new einhorn with the same environment:
105
- reexec_cmdline = 'env VAR=b OINK=b bundle exec --keep-file-descriptors einhorn'
106
-
107
- with_running_einhorn(%W{einhorn -m manual -p #{@server_program} -b 127.0.0.1:#{@port} --reexec-as=#{reexec_cmdline} -d #{@socket_path} -- ruby #{@server_program} VAR},
108
- :env => ENV.to_hash.merge({'VAR' => 'a'})) do |process|
109
-
110
- wait_for_open_port
111
- einhornsh(%W{-d #{@socket_path} -e upgrade})
112
- assert_equal("b", read_from_port, "Should report the upgraded version")
113
-
114
- process.terminate
115
- end
116
- end
117
- end
118
- end
119
- end
120
-
121
- describe 'when invoked with --drop-env-var' do
122
- before do
123
- @dir = prepare_fixture_directory('env_printer')
124
- @port = find_free_port
125
- @server_program = File.join(@dir, "env_printer.rb")
126
- @socket_path = File.join(@dir, "einhorn.sock")
127
- end
128
- after { cleanup_fixtured_directories }
129
-
130
- it %{removes the variable from its children's environment} do
131
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} --drop-env-var=VAR -d #{@socket_path} -- ruby #{@server_program} VAR},
132
- :env => ENV.to_hash.merge({'VAR' => 'a'})) do |process|
133
- wait_for_open_port
134
- assert_equal("a", read_from_port, "Should report $VAR initially")
135
-
136
- einhornsh(%W{-d #{@socket_path} -e upgrade})
137
- assert_equal("", read_from_port, "Should have dropped the variable post-upgrade")
138
-
139
- process.terminate
140
- end
141
- end
142
-
143
- it %{causes an upgrade with --reexec-as to not clobber the new environment} do
144
- reexec_cmdline = 'env VAR2=b bundle exec --keep-file-descriptors einhorn'
145
- with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} --drop-env-var=VAR1 --drop-env-var=VAR2 -d #{@socket_path} --reexec-as=#{reexec_cmdline} -- ruby #{@server_program} VAR1 VAR2},
146
- :env => ENV.to_hash.merge({'VAR1' => 'a', 'VAR2' => 'a'})) do |process|
147
- wait_for_open_port
148
- assert_equal("aa", read_from_port, "Should report both $VAR1 and $VAR2 initially")
149
-
150
- einhornsh(%W{-d #{@socket_path} -e upgrade})
151
- assert_equal("b", read_from_port, "Should have dropped $VAR1 post-upgrade and re-set $VAR2")
152
-
153
- process.terminate
154
- end
155
- end
156
- end
157
- end
@@ -1,88 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
2
-
3
- require 'einhorn'
4
-
5
- class ClientTest < EinhornTestCase
6
- def unserialized_message
7
- {:foo => ['%bar', '%baz']}
8
- end
9
-
10
- def serialized_1_8
11
- "--- %0A:foo: %0A- \"%25bar\"%0A- \"%25baz\"%0A\n"
12
- end
13
-
14
- def serialized_1_9
15
- "---%0A:foo:%0A- ! '%25bar'%0A- ! '%25baz'%0A\n"
16
- end
17
-
18
- def serialized_2_0
19
- "---%0A:foo:%0A- '%25bar'%0A- '%25baz'%0A\n"
20
- end
21
-
22
- def serialized_2_1
23
- "---%0A:foo:%0A- \"%25bar\"%0A- \"%25baz\"%0A\n"
24
- end
25
-
26
- def serialized_options
27
- [serialized_1_8, serialized_1_9, serialized_2_0, serialized_2_1]
28
- end
29
-
30
- describe "when sending a message" do
31
- it "writes a serialized line" do
32
- socket = mock
33
- socket.expects(:write).with {|write| serialized_options.include?(write)}
34
- Einhorn::Client::Transport.send_message(socket, unserialized_message)
35
- end
36
- end
37
-
38
- describe "when receiving a message" do
39
- it "deserializes a single 1.8-style line" do
40
- socket = mock
41
- socket.expects(:readline).returns(serialized_1_8)
42
- result = Einhorn::Client::Transport.receive_message(socket)
43
- assert_equal(result, unserialized_message)
44
- end
45
-
46
- it "deserializes a single 1.9-style line" do
47
- socket = mock
48
- socket.expects(:readline).returns(serialized_1_9)
49
- result = Einhorn::Client::Transport.receive_message(socket)
50
- assert_equal(result, unserialized_message)
51
- end
52
- end
53
-
54
- describe "when {de,}serializing a message" do
55
- it "serializes and escape a message as expected" do
56
- actual = Einhorn::Client::Transport.serialize_message(unserialized_message)
57
- assert(serialized_options.include?(actual), "Actual message is #{actual.inspect}")
58
- end
59
-
60
- it "deserializes and unescape a 1.8-style message as expected" do
61
- actual = Einhorn::Client::Transport.deserialize_message(serialized_1_8)
62
- assert_equal(unserialized_message, actual)
63
- end
64
-
65
- it "deserializes and unescape a 1.9-style message as expected" do
66
- actual = Einhorn::Client::Transport.deserialize_message(serialized_1_9)
67
- assert_equal(unserialized_message, actual)
68
- end
69
-
70
- it "deserializes and unescapes a 2.0-style message as expected" do
71
- actual = Einhorn::Client::Transport.deserialize_message(serialized_2_0)
72
- assert_equal(unserialized_message, actual)
73
- end
74
-
75
- it "deserializes and unescapes a 2.1-style message as expected" do
76
- actual = Einhorn::Client::Transport.deserialize_message(serialized_2_1)
77
- assert_equal(unserialized_message, actual)
78
- end
79
-
80
- it "raises an error when deserializing invalid YAML" do
81
- invalid_serialized = "-%0A\t-"
82
- begin
83
- Einhorn::Client::Transport.deserialize_message(invalid_serialized)
84
- rescue Einhorn::Client::Transport::ParseError
85
- end
86
- end
87
- end
88
- end
@@ -1,49 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../../../_lib'))
2
-
3
- require 'einhorn'
4
-
5
- class InterfaceTest < EinhornTestCase
6
- include Einhorn::Command
7
-
8
- describe "when a command is received" do
9
- it "calls that command" do
10
- conn = stub(:log_debug => nil)
11
- conn.expects(:write).once.with do |message|
12
- # Remove trailing newline
13
- message = message[0...-1]
14
- parsed = YAML.load(URI.unescape(message))
15
- parsed['message'] =~ /Welcome, gdb/
16
- end
17
- request = {
18
- 'command' => 'ehlo',
19
- 'user' => 'gdb'
20
- }
21
- Interface.process_command(conn, YAML.dump(request))
22
- end
23
- end
24
-
25
- describe "when an unrecognized command is received" do
26
- it "calls the unrecognized_command method" do
27
- conn = stub(:log_debug => nil)
28
- Interface.expects(:unrecognized_command).once
29
- request = {
30
- 'command' => 'made-up',
31
- }
32
- Interface.process_command(conn, YAML.dump(request))
33
- end
34
- end
35
-
36
- describe "when a worker ack is received" do
37
- it "registers ack and close the connection" do
38
- conn = stub(:log_debug => nil)
39
- conn.expects(:close).once
40
- conn.expects(:write).never
41
- request = {
42
- 'command' => 'worker:ack',
43
- 'pid' => 1234
44
- }
45
- Einhorn::Command.expects(:register_manual_ack).once.with(1234)
46
- Interface.process_command(conn, YAML.dump(request))
47
- end
48
- end
49
- end
@@ -1,21 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
2
-
3
- require 'einhorn'
4
-
5
- class CommandTest < EinhornTestCase
6
- include Einhorn
7
-
8
- describe "when running quieter" do
9
- it "increases the verbosity threshold" do
10
- Einhorn::State.stubs(:verbosity => 1)
11
- Einhorn::State.expects(:verbosity=).once.with(2).returns(2)
12
- Command.quieter
13
- end
14
-
15
- it "maxes out at 2" do
16
- Einhorn::State.stubs(:verbosity => 2)
17
- Einhorn::State.expects(:verbosity=).never
18
- Command.quieter
19
- end
20
- end
21
- end
@@ -1,89 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
2
-
3
- require 'set'
4
- require 'einhorn'
5
-
6
- module Einhorn::Event
7
- def self.reset
8
- @@loopbreak_reader = nil
9
- @@loopbreak_writer = nil
10
- @@readable = {}
11
- @@writeable = {}
12
- @@timers = {}
13
- end
14
- end
15
-
16
- class EventTest < EinhornTestCase
17
- describe "when running the event loop" do
18
- before do
19
- Einhorn::Event.reset
20
- end
21
-
22
- after do
23
- Einhorn::Event.reset
24
- end
25
-
26
- it "selects on readable descriptors" do
27
- sock1 = stub(:fileno => 4)
28
- sock2 = stub(:fileno => 5)
29
-
30
- conn1 = Einhorn::Event::Connection.open(sock1)
31
- conn2 = Einhorn::Event::Connection.open(sock2)
32
-
33
- IO.expects(:select).once.with do |readers, writers, errs, timeout|
34
- Set.new(readers) == Set.new([sock1, sock2]) &&
35
- writers == [] &&
36
- errs == nil &&
37
- timeout == nil
38
- end.returns([[], [], []])
39
-
40
- Einhorn::Event.loop_once
41
- end
42
-
43
- it "selects on writeable descriptors" do
44
- sock1 = stub(:fileno => 4)
45
- sock2 = stub(:fileno => 5)
46
-
47
- conn1 = Einhorn::Event::Connection.open(sock1)
48
- conn2 = Einhorn::Event::Connection.open(sock2)
49
-
50
- sock2.expects(:write_nonblock).once.raises(Errno::EWOULDBLOCK.new)
51
- conn2.write('Hello!')
52
-
53
- IO.expects(:select).once.with do |readers, writers, errs, timeout|
54
- Set.new(readers) == Set.new([sock1, sock2]) &&
55
- writers == [sock2] &&
56
- errs == nil &&
57
- timeout == nil
58
- end.returns([[], [], []])
59
-
60
- Einhorn::Event.loop_once
61
- end
62
-
63
- it "runs callbacks for ready selectables" do
64
- sock1 = stub(:fileno => 4)
65
- sock2 = stub(:fileno => 5)
66
-
67
- conn1 = Einhorn::Event::Connection.open(sock1)
68
- conn2 = Einhorn::Event::Connection.open(sock2)
69
-
70
- sock2.expects(:write_nonblock).once.raises(Errno::EWOULDBLOCK.new)
71
- conn2.write('Hello!')
72
-
73
- IO.expects(:select).once.with do |readers, writers, errs, timeout|
74
- Set.new(readers) == Set.new([sock1, sock2]) &&
75
- writers == [sock2] &&
76
- errs == nil &&
77
- timeout == nil
78
- end.returns([[sock1], [sock2], []])
79
-
80
- conn1.expects(:notify_readable).once
81
- conn2.expects(:notify_writeable).never
82
-
83
- conn1.expects(:notify_readable).never
84
- conn2.expects(:notify_writeable).once
85
-
86
- Einhorn::Event.loop_once
87
- end
88
- end
89
- end
@@ -1,39 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
2
-
3
- require 'einhorn'
4
-
5
- class WorkerPoolTest < EinhornTestCase
6
- def stub_children
7
- Einhorn::State.stubs(:children).returns(
8
- 1234 => {:type => :worker, :signaled => Set.new(['INT'])},
9
- 1235 => {:type => :state_passer},
10
- 1236 => {:type => :worker, :signaled => Set.new}
11
- )
12
- end
13
-
14
- describe "#workers_with_state" do
15
- before do
16
- stub_children
17
- end
18
-
19
- it "selects only the workers" do
20
- workers_with_state = Einhorn::WorkerPool.workers_with_state
21
- # Sort only needed for Ruby 1.8
22
- assert_equal([
23
- [1234, {:type => :worker, :signaled => Set.new(['INT'])}],
24
- [1236, {:type => :worker, :signaled => Set.new}]
25
- ], workers_with_state.sort)
26
- end
27
- end
28
-
29
- describe "#unsignaled_workers" do
30
- before do
31
- stub_children
32
- end
33
-
34
- it "selects unsignaled workers" do
35
- unsignaled_workers = Einhorn::WorkerPool.unsignaled_workers
36
- assert_equal([1236], unsignaled_workers)
37
- end
38
- end
39
- end
data/test/unit/einhorn.rb DELETED
@@ -1,58 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '../_lib'))
2
-
3
- require 'einhorn'
4
-
5
- class EinhornTest < EinhornTestCase
6
- describe "when sockifying" do
7
- after do
8
- Einhorn::State.sockets = {}
9
- end
10
-
11
- it "correctly parses srv: arguments" do
12
- cmd = ['foo', 'srv:1.2.3.4:123,llama,test', 'bar']
13
- Einhorn.expects(:bind).once.with('1.2.3.4', '123', ['llama', 'test']).returns(4)
14
-
15
- Einhorn.socketify!(cmd)
16
-
17
- assert_equal(['foo', '4', 'bar'], cmd)
18
- end
19
-
20
- it "correctly parses --opt=srv: arguments" do
21
- cmd = ['foo', '--opt=srv:1.2.3.4:456', 'baz']
22
- Einhorn.expects(:bind).once.with('1.2.3.4', '456', []).returns(5)
23
-
24
- Einhorn.socketify!(cmd)
25
-
26
- assert_equal(['foo', '--opt=5', 'baz'], cmd)
27
- end
28
-
29
- it "uses the same fd number for the same server spec" do
30
- cmd = ['foo', '--opt=srv:1.2.3.4:8910', 'srv:1.2.3.4:8910']
31
- Einhorn.expects(:bind).once.with('1.2.3.4', '8910', []).returns(10)
32
-
33
- Einhorn.socketify!(cmd)
34
-
35
- assert_equal(['foo', '--opt=10', '10'], cmd)
36
- end
37
- end
38
-
39
- describe '.update_state' do
40
- it 'correctly updates keys to match new default state hash' do
41
- Einhorn::State.stubs(:default_state).returns(:baz => 23, :foo => 1)
42
- old_state = {:foo => 2, :bar => 2}
43
-
44
- updated_state, message = Einhorn.update_state(Einhorn::State, 'einhorn', old_state)
45
- assert_equal({:baz => 23, :foo => 2}, updated_state)
46
- assert_match(/State format for einhorn has changed/, message)
47
- end
48
-
49
- it 'does not change the state if the format has not changed' do
50
- Einhorn::State.stubs(:default_state).returns(:baz => 23, :foo => 1)
51
- old_state = {:baz => 14, :foo => 1234}
52
-
53
- updated_state, message = Einhorn.update_state(Einhorn::State, 'einhorn', old_state)
54
- assert_equal({:baz => 14, :foo => 1234}, updated_state)
55
- assert(message.nil?)
56
- end
57
- end
58
- end
File without changes