chore-core 1.10.0 → 4.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.
- checksums.yaml +5 -13
- data/LICENSE.txt +1 -1
- data/README.md +172 -153
- data/chore-core.gemspec +3 -3
- data/lib/chore.rb +29 -5
- data/lib/chore/cli.rb +22 -4
- data/lib/chore/configuration.rb +1 -1
- data/lib/chore/consumer.rb +54 -12
- data/lib/chore/fetcher.rb +12 -7
- data/lib/chore/hooks.rb +2 -1
- data/lib/chore/job.rb +19 -0
- data/lib/chore/manager.rb +17 -2
- data/lib/chore/publisher.rb +18 -2
- data/lib/chore/queues/filesystem/consumer.rb +126 -64
- data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
- data/lib/chore/queues/filesystem/publisher.rb +10 -16
- data/lib/chore/queues/sqs.rb +22 -13
- data/lib/chore/queues/sqs/consumer.rb +64 -51
- data/lib/chore/queues/sqs/publisher.rb +26 -17
- data/lib/chore/strategies/consumer/batcher.rb +6 -6
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +7 -6
- data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +120 -0
- data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
- data/lib/chore/strategies/worker/helpers/ipc.rb +87 -0
- data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
- data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
- data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
- data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
- data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
- data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
- data/lib/chore/unit_of_work.rb +2 -1
- data/lib/chore/util.rb +5 -1
- data/lib/chore/version.rb +2 -2
- data/lib/chore/worker.rb +30 -3
- data/spec/chore/cli_spec.rb +2 -2
- data/spec/chore/consumer_spec.rb +1 -5
- data/spec/chore/duplicate_detector_spec.rb +17 -5
- data/spec/chore/fetcher_spec.rb +0 -11
- data/spec/chore/manager_spec.rb +7 -0
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +74 -16
- data/spec/chore/queues/sqs/consumer_spec.rb +117 -78
- data/spec/chore/queues/sqs/publisher_spec.rb +49 -60
- data/spec/chore/queues/sqs_spec.rb +32 -41
- data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +3 -3
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +6 -6
- data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +6 -1
- data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
- data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
- data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
- data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
- data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
- data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
- data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +1 -1
- data/spec/chore/worker_spec.rb +70 -15
- data/spec/spec_helper.rb +1 -1
- data/spec/support/queues/sqs/fake_objects.rb +18 -0
- metadata +53 -29
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chore::Strategy::WorkerInfo do
|
4
|
+
let(:pid) { 1 }
|
5
|
+
let(:worker_info) { Chore::Strategy::WorkerInfo.new(pid) }
|
6
|
+
|
7
|
+
context '#initialize' do
|
8
|
+
it 'should initialize the WorkerInfo with a pid' do
|
9
|
+
wi = Chore::Strategy::WorkerInfo.new(pid)
|
10
|
+
expect(wi.pid).to equal(pid)
|
11
|
+
expect(wi.socket).to equal(nil)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'get_process_mem'
|
3
|
+
|
4
|
+
describe Chore::Strategy::WorkerKiller do
|
5
|
+
let(:memory_limit) { 1024 }
|
6
|
+
let(:request_limit) { 100 }
|
7
|
+
let(:check_cycle) { 16 }
|
8
|
+
let(:worker_killer) { Chore::Strategy::WorkerKiller.new }
|
9
|
+
let(:process_mem_obj) { double('process_mem_obj', bytes: 10) }
|
10
|
+
|
11
|
+
context '#initialize' do
|
12
|
+
it 'should initialize the WorkerKiller correctly' do
|
13
|
+
allow(Chore.config).to receive(:memory_limit_bytes).and_return(memory_limit)
|
14
|
+
allow(Chore.config).to receive(:request_limit).and_return(request_limit)
|
15
|
+
allow(Chore.config).to receive(:check_cycle).and_return(check_cycle)
|
16
|
+
|
17
|
+
wk = Chore::Strategy::WorkerKiller.new
|
18
|
+
expect(wk.instance_variable_get(:@memory_limit)).to equal(memory_limit)
|
19
|
+
expect(wk.instance_variable_get(:@request_limit)).to equal(request_limit)
|
20
|
+
expect(wk.instance_variable_get(:@check_cycle)).to equal(check_cycle)
|
21
|
+
expect(wk.instance_variable_get(:@check_count)).to equal(0)
|
22
|
+
expect(wk.instance_variable_get(:@current_requests)).to equal(0)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#check_memory' do
|
27
|
+
before(:each) do
|
28
|
+
allow(GetProcessMem).to receive(:new).and_return(process_mem_obj)
|
29
|
+
worker_killer.instance_variable_set(:@memory_limit, memory_limit)
|
30
|
+
worker_killer.instance_variable_set(:@check_cycle, check_cycle)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return nil when memory_limit is nil' do
|
34
|
+
worker_killer.instance_variable_set(:@memory_limit, nil)
|
35
|
+
expect(worker_killer.check_memory).to eq(nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should increment the check count by 1' do
|
39
|
+
worker_killer.instance_variable_set(:@check_count, 1)
|
40
|
+
worker_killer.check_memory
|
41
|
+
expect(worker_killer.instance_variable_get(:@check_count)).to eq(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'check_count equals check_cycle' do
|
45
|
+
before(:each) do
|
46
|
+
worker_killer.instance_variable_set(:@check_count, 15)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should check memory' do
|
50
|
+
expect(process_mem_obj).to receive(:bytes)
|
51
|
+
worker_killer.check_memory
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should reset the check_count to zero' do
|
55
|
+
allow(process_mem_obj).to receive(:bytes).and_return(0)
|
56
|
+
worker_killer.check_memory
|
57
|
+
expect(worker_killer.instance_variable_get(:@check_count)).to equal(0)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should exit if the process mem exceeds the memory_limit' do
|
61
|
+
allow(process_mem_obj).to receive(:bytes).and_return(2048)
|
62
|
+
begin
|
63
|
+
worker_killer.check_memory
|
64
|
+
rescue SystemExit=>e
|
65
|
+
expect(e.status).to eq(0)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '#check_requests' do
|
72
|
+
before(:each) do
|
73
|
+
worker_killer.instance_variable_set(:@request_limit, request_limit)
|
74
|
+
worker_killer.instance_variable_set(:@current_requests, 0)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return nil when request_limit is nil' do
|
78
|
+
worker_killer.instance_variable_set(:@request_limit, nil)
|
79
|
+
expect(worker_killer.check_requests).to eq(nil)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should increment current requests' do
|
83
|
+
worker_killer.check_requests
|
84
|
+
expect(worker_killer.instance_variable_get(:@current_requests)).to eq(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should exit when current_requests exceeds request_limit' do
|
88
|
+
worker_killer.instance_variable_set(:@current_requests, request_limit - 1)
|
89
|
+
|
90
|
+
begin
|
91
|
+
worker_killer.check_requests
|
92
|
+
rescue SystemExit=>e
|
93
|
+
expect(e.status).to eq(0)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'chore/signal'
|
3
|
+
require 'chore/strategies/worker/helpers/worker_manager'
|
4
|
+
|
5
|
+
describe Chore::Strategy::WorkerManager do
|
6
|
+
let(:master_socket) { double('master_socket') }
|
7
|
+
|
8
|
+
let(:worker_manager) { Chore::Strategy::WorkerManager.new(master_socket) }
|
9
|
+
|
10
|
+
|
11
|
+
let(:worker_pid_1) { 1 }
|
12
|
+
let(:socket_1) { double('socket_1') }
|
13
|
+
|
14
|
+
let(:worker_pid_2) { 2 }
|
15
|
+
let(:socket_2) { double('socket_2') }
|
16
|
+
|
17
|
+
let(:worker_pid_3) { 3 }
|
18
|
+
|
19
|
+
let(:pid_sock_hash) { { worker_pid_1 => socket_1,
|
20
|
+
worker_pid_2 => socket_2 } }
|
21
|
+
let(:pid_sock_hash_2) { { worker_pid_1 => socket_1 } }
|
22
|
+
|
23
|
+
let(:pid_sock_hash_3) { { worker_pid_1 => socket_1,
|
24
|
+
worker_pid_2 => socket_2,
|
25
|
+
worker_pid_3 => nil } }
|
26
|
+
|
27
|
+
let(:config) { double("config") }
|
28
|
+
let(:worker) { double("worker") }
|
29
|
+
|
30
|
+
let(:worker_info_1) { double('worker_info_1') }
|
31
|
+
let(:worker_info_2) { double('worker_info_2') }
|
32
|
+
|
33
|
+
let(:pid_to_worker) { {
|
34
|
+
worker_pid_1 => worker_info_1,
|
35
|
+
worker_pid_2 => worker_info_2
|
36
|
+
} }
|
37
|
+
let(:socket_to_worker) { {
|
38
|
+
socket_1 => worker_info_1,
|
39
|
+
socket_2 => worker_info_2
|
40
|
+
} }
|
41
|
+
|
42
|
+
context '#include_ipc' do
|
43
|
+
it 'should include the Ipc module' do
|
44
|
+
expect(worker_manager.ipc_help).to eq(:available)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context '#create_and_attach_workers' do
|
49
|
+
before(:each) do
|
50
|
+
allow(worker_manager).to receive(:create_workers).and_yield(2)
|
51
|
+
allow(worker_manager).to receive(:attach_workers).and_return(true)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should call to create replacement workers' do
|
55
|
+
expect(worker_manager).to receive(:create_workers)
|
56
|
+
worker_manager.create_and_attach_workers
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should create a map between new workers to new sockets' do
|
60
|
+
expect(worker_manager).to receive(:attach_workers).with(2)
|
61
|
+
worker_manager.create_and_attach_workers
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context '#respawn_terminated_workers!' do
|
66
|
+
before(:each) do
|
67
|
+
allow(worker_manager).to receive(:create_and_attach_workers).and_return(true)
|
68
|
+
allow(worker_manager).to receive(:reap_workers).and_return(true)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should reap all the terminated worker processes' do
|
72
|
+
expect(worker_manager).to receive(:reap_workers)
|
73
|
+
worker_manager.respawn_terminated_workers!
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should re-create and attach all the workers that died' do
|
77
|
+
expect(worker_manager).to receive(:create_and_attach_workers)
|
78
|
+
worker_manager.respawn_terminated_workers!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context '#stop_workers' do
|
83
|
+
let(:signal) { 'TERM' }
|
84
|
+
before(:each) do
|
85
|
+
allow(Process).to receive(:kill).and_return(nil)
|
86
|
+
allow(worker_manager).to receive(:reap_workers)
|
87
|
+
worker_manager.instance_variable_set(:@pid_to_worker, pid_sock_hash)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should forward the signal received to each of the child processes' do
|
91
|
+
pid_sock_hash.each do |pid, sock|
|
92
|
+
expect(Process).to receive(:kill).with(signal, pid)
|
93
|
+
end
|
94
|
+
worker_manager.stop_workers(signal)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should reap all terminated child processes' do
|
98
|
+
expect(worker_manager).to receive(:reap_workers)
|
99
|
+
worker_manager.stop_workers(signal)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'worker_sockets' do
|
104
|
+
it 'should return a list of socket assoicated with workers' do
|
105
|
+
worker_manager.instance_variable_set(:@socket_to_worker, socket_to_worker)
|
106
|
+
res = worker_manager.worker_sockets
|
107
|
+
expect(res).to eq([socket_1, socket_2])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#ready_workers' do
|
112
|
+
it 'should return a list of workers assoicated with given sockets' do
|
113
|
+
worker_manager.instance_variable_set(:@socket_to_worker, socket_to_worker)
|
114
|
+
allow(worker_info_1).to receive(:reset_start_time!)
|
115
|
+
res = worker_manager.ready_workers([socket_1])
|
116
|
+
expect(res).to eq([worker_info_1])
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should yield when a block is passed to it' do
|
120
|
+
worker_manager.instance_variable_set(:@socket_to_worker, socket_to_worker)
|
121
|
+
allow(worker_info_1).to receive(:reset_start_time!)
|
122
|
+
expect{ |b| worker_manager.ready_workers([socket_1], &b) }.to yield_control
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context '#create_workers' do
|
127
|
+
before(:each) do
|
128
|
+
allow(worker_manager).to receive(:fork).and_yield
|
129
|
+
allow(worker_manager).to receive(:run_worker_instance)
|
130
|
+
allow(Chore).to receive(:config).and_return(config)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should fork it running process till we have the right optimized number of workers and return the number of workers it created' do
|
134
|
+
allow(config).to receive(:num_workers).and_return(1)
|
135
|
+
expect(worker_manager).to receive(:fork).once
|
136
|
+
expect(worker_manager).to receive(:run_worker_instance).once
|
137
|
+
worker_manager.send(:create_workers)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should raise an exception if an inconsistent number of workers are created' do
|
141
|
+
allow(config).to receive(:num_workers).and_return(0)
|
142
|
+
allow(worker_manager).to receive(:inconsistent_worker_number).and_return(true)
|
143
|
+
expect(worker_manager).to receive(:inconsistent_worker_number)
|
144
|
+
expect { worker_manager.send(:create_workers) }.to raise_error(RuntimeError)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should call the block passed to it with the number of workers it created' do
|
148
|
+
allow(config).to receive(:num_workers).and_return(0)
|
149
|
+
allow(worker_manager).to receive(:inconsistent_worker_number).and_return(false)
|
150
|
+
expect { |b| worker_manager.send(:create_workers, &b) }.to yield_control.once
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context '#inconsistent_worker_number' do
|
155
|
+
it 'should check if the worker numbers match the number configured' do
|
156
|
+
allow(Chore).to receive(:config).and_return(config)
|
157
|
+
allow(config).to receive(:num_workers).and_return(2)
|
158
|
+
worker_manager.instance_variable_set(:@pid_to_worker, pid_to_worker)
|
159
|
+
res_false = worker_manager.send(:inconsistent_worker_number)
|
160
|
+
allow(config).to receive(:num_workers).and_return(4)
|
161
|
+
res_true = worker_manager.send(:inconsistent_worker_number)
|
162
|
+
expect(res_true).to be(true)
|
163
|
+
expect(res_false).to be(false)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context '#run_worker_instance' do
|
168
|
+
before(:each) do
|
169
|
+
allow(Chore::Strategy::PreforkedWorker).to receive(:new).and_return(worker)
|
170
|
+
allow(worker).to receive(:start_worker).and_return(true)
|
171
|
+
allow(worker_manager).to receive(:exit).and_return(true)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should create a PreforkedWorker object and start it' do
|
175
|
+
expect(Chore::Strategy::PreforkedWorker).to receive(:new).and_return(worker)
|
176
|
+
expect(worker).to receive(:start_worker)
|
177
|
+
worker_manager.send(:run_worker_instance)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should ensure that the process exits when the PreforkedWorker completes' do
|
181
|
+
expect(worker_manager).to receive(:exit).with(true)
|
182
|
+
worker_manager.send(:run_worker_instance)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context '#attach_workers' do
|
187
|
+
before(:each) do
|
188
|
+
allow(worker_manager).to receive(:create_worker_sockets).and_return([socket_1, socket_2])
|
189
|
+
allow(worker_manager).to receive(:read_msg).and_return(worker_pid_1, worker_pid_2)
|
190
|
+
allow(worker_manager).to receive(:kill_unattached_workers).and_return(true)
|
191
|
+
worker_manager.instance_variable_set(:@pid_to_worker, pid_to_worker)
|
192
|
+
worker_manager.instance_variable_set(:@socket_to_worker, {})
|
193
|
+
allow(worker_info_1).to receive(:socket=)
|
194
|
+
allow(worker_info_2).to receive(:socket=)
|
195
|
+
allow(worker_manager).to receive(:select_sockets).and_return([[socket_1], [], []], [[socket_2], [], []])
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should add as many sockets as the number passed to it as a param' do
|
199
|
+
expect(worker_manager).to receive(:read_msg).twice
|
200
|
+
worker_manager.send(:attach_workers, 2)
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'should select on each socket to make sure its readable' do
|
204
|
+
expect(worker_manager).to receive(:select_sockets).twice
|
205
|
+
worker_manager.send(:attach_workers, 2)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'should get the PID from each socket it creates and map that to the worker that it is connected to' do
|
209
|
+
worker_manager.send(:attach_workers, 2)
|
210
|
+
expect(worker_manager.instance_variable_get(:@socket_to_worker)).to eq(socket_to_worker)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should kill any unattached workers' do
|
214
|
+
expect(worker_manager).to receive(:kill_unattached_workers)
|
215
|
+
worker_manager.send(:attach_workers, 2)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should close sockets that failed to get a connection by timing out' do
|
219
|
+
allow(worker_manager).to receive(:select_sockets).and_return([[socket_1], [], []], [nil, nil, nil])
|
220
|
+
expect(socket_1).not_to receive(:close)
|
221
|
+
expect(socket_2).to receive(:close)
|
222
|
+
worker_manager.send(:attach_workers, 2)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should close sockets that failed to get a connection by econnreset' do
|
226
|
+
allow(worker_manager).to receive(:select_sockets).with(socket_1, nil, 2).and_return([[socket_1], [], []])
|
227
|
+
allow(worker_manager).to receive(:select_sockets).with(socket_2, nil, 2).and_raise(Errno::ECONNRESET)
|
228
|
+
expect(socket_1).not_to receive(:close)
|
229
|
+
expect(socket_2).to receive(:close)
|
230
|
+
worker_manager.send(:attach_workers, 2)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "#create_worker_sockets" do
|
235
|
+
it 'should return an array of sockets equal to the number passed to it' do
|
236
|
+
allow(worker_manager).to receive(:add_worker_socket).and_return(socket_1, socket_2, socket_2, socket_1)
|
237
|
+
num = 3
|
238
|
+
res = worker_manager.send(:create_worker_sockets, num)
|
239
|
+
expect(res.size).to eq(num)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context "#kill_unattached_workers" do
|
244
|
+
it 'should send a kill -9 to PIDs that do not have a socket attached to them' do
|
245
|
+
worker_manager.instance_variable_set(:@pid_to_worker, pid_to_worker)
|
246
|
+
allow(worker_info_1).to receive(:socket).and_return(socket_1)
|
247
|
+
allow(worker_info_2).to receive(:socket).and_return(nil)
|
248
|
+
allow(Process).to receive(:kill).and_return(true)
|
249
|
+
expect(Process).to receive(:kill).with('KILL', worker_pid_2).once
|
250
|
+
worker_manager.send(:kill_unattached_workers)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context "#reap_workers" do
|
255
|
+
before(:each) do
|
256
|
+
worker_manager.instance_variable_set(:@pid_to_worker, pid_to_worker)
|
257
|
+
allow(worker_info_1).to receive(:socket).and_return(socket_1)
|
258
|
+
allow(worker_info_1).to receive(:pid).and_return(worker_pid_1)
|
259
|
+
allow(worker_info_2).to receive(:socket).and_return(socket_2)
|
260
|
+
allow(worker_info_2).to receive(:pid).and_return(worker_pid_2)
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'should run through each worker-pid map and delete the worker pids from the hash, if they were dead' do
|
264
|
+
allow(worker_manager).to receive(:reap_process).and_return(false, true)
|
265
|
+
allow(socket_2).to receive(:close)
|
266
|
+
expect(worker_manager.instance_variable_get(:@pid_to_worker).size).to eq(2)
|
267
|
+
worker_manager.send(:reap_workers)
|
268
|
+
expect(worker_manager.instance_variable_get(:@pid_to_worker).size).to eq(1)
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'closes the socket associated with the reaped worker' do
|
272
|
+
allow(worker_manager).to receive(:reap_process).and_return(false, true)
|
273
|
+
expect(socket_2).to receive(:close)
|
274
|
+
worker_manager.send(:reap_workers)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'should not change the worker pids map, if all the childern are running' do
|
278
|
+
allow(worker_manager).to receive(:reap_process).and_return(false, false)
|
279
|
+
expect(worker_manager.instance_variable_get(:@pid_to_worker).size).to eq(2)
|
280
|
+
worker_manager.send(:reap_workers)
|
281
|
+
expect(worker_manager.instance_variable_get(:@pid_to_worker).size).to eq(2)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "#reap_worker" do
|
286
|
+
it 'should return true if the pid was dead' do
|
287
|
+
allow(Process).to receive(:wait).and_return(worker_pid_1)
|
288
|
+
res = worker_manager.send(:reap_process, worker_pid_1)
|
289
|
+
expect(res).to eq(true)
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'should return false if the pid was running' do
|
293
|
+
allow(Process).to receive(:wait).and_return(nil)
|
294
|
+
res = worker_manager.send(:reap_process, worker_pid_1)
|
295
|
+
expect(res).to eq(false)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should return true if the pid was dead' do
|
299
|
+
allow(Process).to receive(:wait).and_raise(Errno::ECHILD)
|
300
|
+
res = worker_manager.send(:reap_process, worker_pid_1)
|
301
|
+
expect(res).to eq(true)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chore::Strategy::PreForkedWorkerStrategy do
|
4
|
+
let(:manager) { double('manager') }
|
5
|
+
let(:socket) { double('socket') }
|
6
|
+
let(:pipe) { double('pipe') }
|
7
|
+
let(:worker) { double('worker') }
|
8
|
+
let(:worker_manager) { Chore::Strategy::WorkerManager.new(socket) }
|
9
|
+
let(:strategy) { Chore::Strategy::PreForkedWorkerStrategy.new(manager) }
|
10
|
+
let(:work_distributor) { Chore::Strategy::WorkDistributor }
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
allow_any_instance_of(Chore::Strategy::PreForkedWorkerStrategy).to receive(:trap_signals).and_return(true)
|
14
|
+
strategy.instance_variable_set(:@worker_manager, worker_manager)
|
15
|
+
end
|
16
|
+
|
17
|
+
context '#start' do
|
18
|
+
it 'should create, attach workers and start the worker manager' do
|
19
|
+
allow(worker_manager).to receive(:create_and_attach_workers)
|
20
|
+
allow(strategy).to receive(:worker_assignment_thread).and_return(true)
|
21
|
+
|
22
|
+
expect(worker_manager).to receive(:create_and_attach_workers)
|
23
|
+
expect(strategy).to receive(:worker_assignment_thread)
|
24
|
+
strategy.start
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context '#stop!' do
|
29
|
+
it 'should set the system\'s running state to false' do
|
30
|
+
strategy.instance_variable_set(:@running, true)
|
31
|
+
strategy.stop!
|
32
|
+
expect(strategy.instance_variable_get(:@running)).to eq false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context '#worker_assignment_thread' do
|
37
|
+
before(:each) do
|
38
|
+
allow(Thread).to receive(:new).and_return(true)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'create a new thread with the worker_assignment_loop' do
|
42
|
+
allow(strategy).to receive(:worker_assignment_loop).and_return(true)
|
43
|
+
expect(Thread).to receive(:new).once.and_yield
|
44
|
+
expect(strategy).to receive(:worker_assignment_loop)
|
45
|
+
expect(Process).to receive(:exit)
|
46
|
+
strategy.send(:worker_assignment_thread)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'rescues a \'TerribleMistake\' exception and performs a shutdown of chore' do
|
50
|
+
allow(strategy).to receive(:worker_assignment_loop).and_raise(Chore::TerribleMistake)
|
51
|
+
allow(manager).to receive(:shutdown!)
|
52
|
+
allow(Thread).to receive(:new).and_yield
|
53
|
+
expect(Process).to receive(:exit)
|
54
|
+
strategy.send(:worker_assignment_thread)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#worker_assignment_loop' do
|
59
|
+
before(:each) do
|
60
|
+
allow(socket).to receive(:eof?).and_return(false)
|
61
|
+
|
62
|
+
allow(strategy).to receive(:running?).and_return(true, false)
|
63
|
+
strategy.instance_variable_set(:@self_read, pipe)
|
64
|
+
allow(strategy).to receive(:select_sockets).and_return([[socket], nil, nil])
|
65
|
+
allow(strategy).to receive(:handle_self_pipe_signal).and_return(true)
|
66
|
+
allow(strategy).to receive(:fetch_and_assign_jobs).and_return(true)
|
67
|
+
|
68
|
+
allow(worker_manager).to receive(:worker_sockets).and_return([socket])
|
69
|
+
allow(worker_manager).to receive(:ready_workers).and_yield([worker])
|
70
|
+
allow(worker_manager).to receive(:destroy_expired!)
|
71
|
+
|
72
|
+
allow(work_distributor).to receive(:fetch_and_assign_jobs)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should terminate when @running is set to false' do
|
76
|
+
allow(strategy).to receive(:running?).and_return(false)
|
77
|
+
expect(strategy).to receive(:select_sockets).exactly(0).times
|
78
|
+
strategy.send(:worker_assignment_loop)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should get the worker_sockets' do
|
82
|
+
expect(worker_manager).to receive(:worker_sockets)
|
83
|
+
strategy.send(:worker_assignment_loop)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should handle no sockets being ready' do
|
87
|
+
allow(strategy).to receive(:select_sockets).and_return(nil)
|
88
|
+
expect(strategy).to receive(:select_sockets).once
|
89
|
+
strategy.send(:worker_assignment_loop)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should handle signals if alerted on a self pipe' do
|
93
|
+
allow(strategy).to receive(:select_sockets).and_return([[pipe], nil, nil])
|
94
|
+
expect(strategy).to receive(:handle_signal).once
|
95
|
+
strategy.send(:worker_assignment_loop)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should check if sockets have hit EOF' do
|
99
|
+
allow(strategy).to receive(:handle_signal)
|
100
|
+
expect(socket).to receive(:eof?).once.and_return(false)
|
101
|
+
strategy.send(:worker_assignment_loop)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should not assign jobs if sockets have hit EOF' do
|
105
|
+
allow(strategy).to receive(:handle_signal)
|
106
|
+
allow(socket).to receive(:eof?).and_return(true)
|
107
|
+
expect(work_distributor).to_not receive(:fetch_and_assign_jobs)
|
108
|
+
strategy.send(:worker_assignment_loop)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should handle fetch and assign jobs when workers are ready' do
|
112
|
+
expect(work_distributor).to receive(:fetch_and_assign_jobs).with([worker], manager).once
|
113
|
+
strategy.send(:worker_assignment_loop)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context '#handle_signal' do
|
118
|
+
before(:each) do
|
119
|
+
strategy.instance_variable_set(:@self_read, pipe)
|
120
|
+
allow(pipe).to receive(:read_nonblock).and_return(nil)
|
121
|
+
|
122
|
+
allow(worker_manager).to receive(:respawn_terminated_workers!).and_return(true)
|
123
|
+
allow(worker_manager).to receive(:stop_workers).and_return(true)
|
124
|
+
allow(manager).to receive(:shutdown!).and_return(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should respawn terminated workers in the event of a SIGCHLD' do
|
128
|
+
allow(pipe).to receive(:read_nonblock).and_return('1')
|
129
|
+
expect(worker_manager).to receive(:respawn_terminated_workers!).once
|
130
|
+
|
131
|
+
strategy.send(:handle_signal)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should signal its children, and shutdown in the event of one of INT, QUIT or TERM signals' do
|
135
|
+
allow(pipe).to receive(:read_nonblock).and_return('2', '3', '4')
|
136
|
+
expect(worker_manager).to receive(:stop_workers).exactly(3).times
|
137
|
+
expect(manager).to receive(:shutdown!).exactly(3).times
|
138
|
+
3.times do
|
139
|
+
strategy.send(:handle_signal)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should propagte the signal it receives to its children' do
|
144
|
+
allow(pipe).to receive(:read_nonblock).and_return('3')
|
145
|
+
expect(worker_manager).to receive(:stop_workers).with(:QUIT)
|
146
|
+
strategy.send(:handle_signal)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should reset logs on a USR1 signal' do
|
150
|
+
allow(pipe).to receive(:read_nonblock).and_return('5')
|
151
|
+
expect(Chore).to receive(:reopen_logs)
|
152
|
+
strategy.send(:handle_signal)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should not preform any task when an unhandled signal is called' do
|
156
|
+
allow(pipe).to receive(:read_nonblock).and_return('9')
|
157
|
+
expect(worker_manager).to receive(:respawn_terminated_workers!).exactly(0).times
|
158
|
+
expect(worker_manager).to receive(:stop_workers).exactly(0).times
|
159
|
+
expect(manager).to receive(:shutdown!).exactly(0).times
|
160
|
+
strategy.send(:handle_signal)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context '#trap_signals' do
|
165
|
+
before(:each) do
|
166
|
+
allow_any_instance_of(Chore::Strategy::PreForkedWorkerStrategy).to receive(:trap_signals).and_call_original
|
167
|
+
end
|
168
|
+
|
169
|
+
let(:signals) { { '1' => 'QUIT' } }
|
170
|
+
|
171
|
+
it 'should reset signals' do
|
172
|
+
allow(Chore::Signal).to receive(:reset)
|
173
|
+
expect(Chore::Signal).to receive(:reset)
|
174
|
+
strategy.send(:trap_signals, {}, pipe)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should trap the signals passed to it' do
|
178
|
+
allow(Chore::Signal).to receive(:reset)
|
179
|
+
expect(Chore::Signal).to receive(:trap).with('QUIT').once
|
180
|
+
strategy.send(:trap_signals, signals, pipe)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|