trident 0.1.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.
@@ -0,0 +1,233 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class Trident::PoolTest < MiniTest::Should::TestCase
4
+
5
+ setup do
6
+ SignalHandler.stubs(:reset_for_fork)
7
+
8
+ PoolHandler.constants(false).each do |c|
9
+ PoolHandler.send(:remove_const, c) if c =~ /^Test/
10
+ end
11
+ env = <<-EOS
12
+ class TestPoolWorker
13
+ def initialize(o)
14
+ @o = o
15
+ end
16
+ def start
17
+ sleep(@o['sleep']) if @o['sleep']
18
+ end
19
+ end
20
+ EOS
21
+ signal_mappings = {'stop_forcefully' => 'KILL', 'stop_gracefully' => 'TERM'}
22
+ @handler = PoolHandler.new("foo", "TestPoolWorker", env, signal_mappings, {})
23
+ end
24
+
25
+ context "#spawn_worker" do
26
+
27
+ should "fork a worker" do
28
+ pool = Pool.new("foo", @handler, 1, 'sleep' => 0.1)
29
+ assert_empty pool.workers
30
+ pool.send(:spawn_worker)
31
+ assert_equal 1, pool.workers.size
32
+ Process.waitpid(pool.workers.first)
33
+ assert $?.success?
34
+ end
35
+
36
+ end
37
+
38
+ context "#kill_worker" do
39
+
40
+ should "kill a worker" do
41
+ pool = Pool.new("foo", @handler, 1, 'sleep' => 1)
42
+ pool.send(:spawn_worker)
43
+ pid = pool.workers.first
44
+
45
+ pool.send(:kill_worker, pid, 'stop_forcefully')
46
+ Process.waitpid(pid)
47
+ assert ! $?.success?
48
+ assert_empty pool.workers
49
+ end
50
+
51
+ should "kill a worker with specific signal" do
52
+ pool = Pool.new("foo", @handler, 1, 'sleep' => 1)
53
+ pool.send(:spawn_worker)
54
+ pid = pool.workers.first
55
+
56
+ Process.expects(:kill).with("TERM", pid)
57
+ pool.send(:kill_worker, pid, 'stop_gracefully')
58
+ end
59
+
60
+ end
61
+
62
+ context "#spawn_workers" do
63
+
64
+ should "start multiple workers" do
65
+ pool = Pool.new("foo", @handler, 4, 'sleep' => 1)
66
+ pool.send(:spawn_workers, 4)
67
+ assert_equal 4, pool.workers.size
68
+ end
69
+
70
+ end
71
+
72
+ context "#kill_workers" do
73
+
74
+ should "kill multiple workers, most recent first" do
75
+ pool = Pool.new("foo", @handler, 4, 'sleep' => 1)
76
+ pool.send(:spawn_workers, 4)
77
+ orig_workers = pool.workers.dup
78
+ assert_equal 4, orig_workers.size
79
+
80
+ pool.send(:kill_workers, 3, 'stop_forcefully')
81
+ assert_equal 1, pool.workers.size
82
+ assert_equal orig_workers.first, pool.workers.first
83
+ end
84
+
85
+ end
86
+
87
+ context "#cleanup_dead_workers" do
88
+
89
+ should "stop tracking workers that have died" do
90
+ pool = Pool.new("foo", @handler, 4, 'sleep' => 0)
91
+ pool.send(:spawn_workers, 4)
92
+
93
+ sleep 0.1
94
+ assert_equal 4, pool.workers.size
95
+ pool.send(:cleanup_dead_workers)
96
+ assert_equal 0, pool.workers.size
97
+ end
98
+
99
+ should "block waiting for workers that have died when blocking" do
100
+ pool = Pool.new("foo", @handler, 1, 'sleep' => 0.2)
101
+ pool.send(:spawn_workers, 1)
102
+ assert_equal 1, pool.workers.size
103
+
104
+ thread = Thread.new { pool.send(:cleanup_dead_workers, true) }
105
+ sleep(0.1)
106
+ assert_equal 1, pool.workers.size
107
+ thread.join
108
+ assert_equal 0, pool.workers.size
109
+ end
110
+
111
+ should "not block waiting for workers that have died when not-blocking" do
112
+ pool = Pool.new("foo", @handler, 1, 'sleep' => 0.1)
113
+ pool.send(:spawn_workers, 1)
114
+ assert_equal 1, pool.workers.size
115
+
116
+ pool.send(:cleanup_dead_workers, false)
117
+ assert_equal 1, pool.workers.size
118
+ end
119
+
120
+ should "cleanup workers that have died even if already waited on" do
121
+ pool = Pool.new("foo", @handler, 4, 'sleep' => 0)
122
+ pool.send(:spawn_workers, 4)
123
+
124
+ # Calling process.wait on a pid that was already waited on throws a ECHLD
125
+ Process.waitall
126
+ assert_equal 4, pool.workers.size
127
+ pool.send(:cleanup_dead_workers, false)
128
+
129
+ assert_equal 0, pool.workers.size
130
+ end
131
+
132
+
133
+ end
134
+
135
+ context "#maintain_worker_count" do
136
+
137
+ should "spawn workers when count is low" do
138
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
139
+ assert_empty pool.workers
140
+
141
+ pool.send(:maintain_worker_count, 'stop_gracefully')
142
+ assert_equal 2, pool.workers.size
143
+ end
144
+
145
+ should "kill workers when count is high" do
146
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
147
+ pool.send(:spawn_workers, 4)
148
+ assert_equal 4, pool.workers.size
149
+
150
+ pool.send(:maintain_worker_count, 'stop_gracefully')
151
+ assert_equal 2, pool.workers.size
152
+ end
153
+
154
+ should "kill workers with given action when count is high" do
155
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
156
+ pool.send(:spawn_workers, 4)
157
+ assert_equal 4, pool.workers.size
158
+
159
+ Process.expects(:kill).with("KILL", pool.workers.to_a[-1])
160
+ Process.expects(:kill).with("KILL", pool.workers.to_a[-2])
161
+ pool.send(:maintain_worker_count, 'stop_forcefully')
162
+
163
+ pool.send(:spawn_workers, 2)
164
+ Process.expects(:kill).with("TERM", pool.workers.to_a[-1])
165
+ Process.expects(:kill).with("TERM", pool.workers.to_a[-2])
166
+ pool.send(:maintain_worker_count, 'stop_gracefully')
167
+ end
168
+
169
+ should "do nothing when count is correct" do
170
+ Process.expects(:kill).never
171
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
172
+ pool.send(:spawn_workers, 2)
173
+ orig_workers = pool.workers.dup
174
+ assert_equal 2, orig_workers.size
175
+
176
+ pool.send(:maintain_worker_count, 'stop_gracefully')
177
+ assert_equal 2, pool.workers.size
178
+ assert_equal orig_workers, pool.workers
179
+ end
180
+
181
+ end
182
+
183
+ context "#start" do
184
+
185
+ should "start up the workers" do
186
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
187
+ pool.start
188
+ assert_equal 2, pool.workers.size
189
+ end
190
+
191
+ end
192
+
193
+ context "#stop" do
194
+
195
+ should "stop the workers" do
196
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
197
+ pool.start
198
+ assert_equal 2, pool.workers.size
199
+ pool.stop
200
+ assert_empty pool.workers
201
+ end
202
+
203
+ end
204
+
205
+ context "#wait" do
206
+
207
+ should "block till all workers complete" do
208
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.1)
209
+ pool.start
210
+ assert_equal 2, pool.workers.size
211
+ pool.wait
212
+ assert_empty pool.workers
213
+ end
214
+
215
+ end
216
+
217
+ context "#update" do
218
+
219
+ should "update monitored workers" do
220
+ pool = Pool.new("foo", @handler, 2, 'sleep' => 0.2)
221
+ pool.start
222
+ orig_workers = pool.workers.dup
223
+ assert_equal 2, orig_workers.size
224
+ Process.kill("KILL", orig_workers.first)
225
+ sleep(0.1)
226
+ assert_equal orig_workers, pool.workers
227
+ pool.update
228
+ refute_equal orig_workers, pool.workers
229
+ end
230
+
231
+ end
232
+
233
+ end
@@ -0,0 +1,262 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class Trident::SignalHandlerTest < MiniTest::Should::TestCase
4
+
5
+ class Target
6
+ attr_accessor :received
7
+
8
+ def initialize
9
+ @received = []
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ @received << [method, args, block]
14
+ method =~ /action_(.*)/ ? $1.to_sym : :noaction
15
+ end
16
+
17
+ def respond_to_missing?(name, include_private = false)
18
+ name !~ /nomethod/
19
+ end
20
+ end
21
+
22
+ def do_in_child
23
+ read_from_child, write_from_child = IO.pipe
24
+
25
+ pid = fork do
26
+ read_from_child.close
27
+ result = yield
28
+ Marshal.dump(result, write_from_child)
29
+ exit!(0) # skips exit handlers.
30
+ end
31
+
32
+ [pid, read_from_child]
33
+ write_from_child.close
34
+ result = read_from_child.read
35
+ Process.wait(pid)
36
+ raise "child failed" if result.empty?
37
+ Marshal.load(result)
38
+ end
39
+
40
+ context "#initialize" do
41
+
42
+ should "add in CHLD handler" do
43
+ handler = SignalHandler.new({}, Target.new)
44
+ assert_equal({"SIGCHLD" => ["update"]}, handler.signal_mappings)
45
+ end
46
+
47
+ should "allow CHLD handler replacement" do
48
+ handler = SignalHandler.new({"CHLD" => "foo"}, Target.new)
49
+ assert_equal({"SIGCHLD" => ["foo"]}, handler.signal_mappings)
50
+ end
51
+
52
+ end
53
+
54
+ context "#signal_mappings==" do
55
+
56
+ should "normalize signal names" do
57
+ handler = SignalHandler.new({}, Target.new)
58
+ handler.send :signal_mappings=,
59
+ {"int" => "foo", "sigterm" => "bar",
60
+ "USR1" => "baz", "SIGUSR2" => ["bum", "hum"]}
61
+
62
+ assert_equal({"SIGCHLD" => ["update"], "SIGINT" => ["foo"],
63
+ "SIGTERM" => ["bar"], "SIGUSR1" => ["baz"],
64
+ "SIGUSR2" => ["bum", "hum"]}, handler.signal_mappings)
65
+ end
66
+
67
+ should "fail for duplicate signals" do
68
+ handler = SignalHandler.new({}, Target.new)
69
+ signals = {"int" => "foo", "sigint" => "bar"}
70
+ assert_raises(ArgumentError) { handler.send :signal_mappings=, signals }
71
+ end
72
+
73
+ end
74
+
75
+ context "#setup_self_pipe" do
76
+
77
+ should "create new pipes" do
78
+ handler = SignalHandler.new({}, Target.new)
79
+ assert_equal 0, handler.send(:self_pipe).size
80
+ handler.send :setup_self_pipe
81
+ assert_equal 2, handler.send(:self_pipe).size
82
+ end
83
+
84
+ should "replace pipes with new ones" do
85
+ handler = SignalHandler.new({}, Target.new)
86
+ handler.send :setup_self_pipe
87
+ old = handler.send(:self_pipe).dup
88
+ assert_equal 2, old.size
89
+ handler.send :setup_self_pipe
90
+ new = handler.send(:self_pipe).dup
91
+ assert_equal 2, new.size
92
+ refute_equal old, new
93
+ end
94
+
95
+ end
96
+
97
+ context "#setup_signal_handlers" do
98
+
99
+ should "trap given signals" do
100
+ signals = {"int" => "foo", "term" => "bar"}
101
+ handler = SignalHandler.new(signals, Target.new)
102
+
103
+ handler.expects(:trap_deferred).with("SIGINT")
104
+ handler.expects(:trap_deferred).with("SIGTERM")
105
+ handler.expects(:trap_deferred).with("SIGCHLD")
106
+ handler.send(:setup_signal_handlers)
107
+ end
108
+
109
+ should "save original signals" do
110
+ signals = {"int" => "foo", "term" => "bar"}
111
+ handler = SignalHandler.new(signals, Target.new)
112
+
113
+ handler.stubs(:trap_deferred)
114
+ handler.send(:setup_signal_handlers)
115
+ assert_equal({"SIGCHLD" => nil, "SIGINT" => nil, "SIGTERM" => nil},
116
+ handler.original_signal_handlers)
117
+ end
118
+
119
+ should "fail for unhandled methods" do
120
+ signals = {"term" => "nomethod"}
121
+ handler = SignalHandler.new(signals, Target.new)
122
+
123
+ handler.stubs(:trap_deferred)
124
+ assert_raises(ArgumentError) { handler.send(:setup_signal_handlers) }
125
+ end
126
+
127
+ end
128
+
129
+ context "#reset_signal_handlers" do
130
+
131
+ should "reset signals" do
132
+ signals = {"int" => "foo", "term" => "bar"}
133
+ handler = SignalHandler.new(signals, Target.new)
134
+
135
+ handler.stubs(:trap_deferred)
136
+ handler.send(:setup_signal_handlers)
137
+
138
+ handler.expects(:trap).with("SIGINT", nil)
139
+ handler.expects(:trap).with("SIGTERM", nil)
140
+ handler.expects(:trap).with("SIGCHLD", nil)
141
+ handler.send(:reset_signal_handlers)
142
+ assert_empty handler.original_signal_handlers
143
+ end
144
+
145
+ end
146
+
147
+ context "#handle_signal_queue" do
148
+
149
+ setup do
150
+ signals = {"int" => "foo", "term" => "bar"}
151
+ @target = Target.new
152
+ @handler = SignalHandler.new(signals, @target)
153
+ end
154
+
155
+ should "do nothing when queue empty" do
156
+ assert_empty @handler.signal_queue
157
+ assert_nil @handler.send(:handle_signal_queue)
158
+ assert_empty @target.received
159
+ end
160
+
161
+ should "do nothing if signal unknown" do
162
+ @handler.signal_queue << "SIGUSR1"
163
+ assert_nil @handler.send(:handle_signal_queue)
164
+ assert_empty @target.received
165
+ end
166
+
167
+ should "call target for known signal" do
168
+ @handler.signal_queue << "SIGINT"
169
+ assert_equal :noaction, @handler.send(:handle_signal_queue)
170
+ assert_equal [[:foo, [], nil]], @target.received
171
+ end
172
+
173
+ end
174
+
175
+ context "#snooze/wakeup" do
176
+
177
+ should "block until woken" do
178
+ handler = SignalHandler.new({}, Target.new)
179
+ handler.send(:setup_self_pipe)
180
+ thread = Thread.new { handler.snooze }
181
+ sleep 0.1
182
+ assert thread.alive?
183
+ handler.wakeup
184
+ sleep 0.1
185
+ refute thread.alive?
186
+ assert_equal ".", thread.value
187
+ end
188
+
189
+ end
190
+
191
+ context "#start/stop" do
192
+
193
+ should "block until woken" do
194
+ handler = SignalHandler.new({}, Target.new)
195
+ handler.stubs(:trap)
196
+ thread = Thread.new { handler.start }
197
+ sleep 0.1
198
+ assert thread.alive?
199
+ handler.stop
200
+ sleep 0.1
201
+ refute thread.alive?
202
+ end
203
+
204
+ end
205
+
206
+ context ".start" do
207
+
208
+ should "fail if already instantiated" do
209
+ SignalHandler.instance = SignalHandler.new({}, Target.new)
210
+ assert_raises(RuntimeError) { SignalHandler.start({}, Target.new) }
211
+ end
212
+
213
+ end
214
+
215
+ context ".stop" do
216
+
217
+ should "fail if not instantiated" do
218
+ SignalHandler.instance = nil
219
+ assert_raises(RuntimeError) { SignalHandler.stop }
220
+ end
221
+
222
+ end
223
+
224
+ context "api" do
225
+
226
+ should "react to signals" do
227
+ fc = ForkChild.new do
228
+ target = Target.new
229
+ SignalHandler.start({"int" => "foo", "term" => "bar", "usr1" => "action_break"}, target)
230
+ target.received
231
+ end
232
+
233
+ sleep 0.1
234
+ Process.kill("TERM", fc.pid)
235
+ sleep 0.1
236
+ Process.kill("INT", fc.pid)
237
+ sleep 0.1
238
+ Process.kill("USR1", fc.pid)
239
+
240
+ received = fc.wait
241
+ assert_equal [[:bar, [], nil], [:foo, [], nil], [:action_break, [], nil]], received
242
+ end
243
+
244
+ should "honor signal queue limit" do
245
+ fc = ForkChild.new do
246
+ $stderr.reopen("/dev/null")
247
+ target = Target.new
248
+ SignalHandler.start({"int" => "foo", "term" => "bar", "usr1" => "action_break"}, target)
249
+ target.received
250
+ end
251
+ sleep 0.1
252
+
253
+ 50.times { Process.kill("TERM", fc.pid) }
254
+ sleep 0.2
255
+ Process.kill("USR1", fc.pid)
256
+ received = fc.wait
257
+ assert received.size > 0
258
+ assert received.size < 50
259
+ end
260
+
261
+ end
262
+ end