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
@@ -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