trident 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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