chore-core 1.8.2 → 3.2.3

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 (51) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +6 -0
  3. data/chore-core.gemspec +1 -0
  4. data/lib/chore.rb +11 -5
  5. data/lib/chore/cli.rb +21 -2
  6. data/lib/chore/consumer.rb +15 -5
  7. data/lib/chore/fetcher.rb +12 -7
  8. data/lib/chore/hooks.rb +2 -1
  9. data/lib/chore/job.rb +17 -0
  10. data/lib/chore/manager.rb +18 -2
  11. data/lib/chore/queues/filesystem/consumer.rb +116 -59
  12. data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
  13. data/lib/chore/queues/filesystem/publisher.rb +12 -18
  14. data/lib/chore/queues/sqs/consumer.rb +6 -21
  15. data/lib/chore/strategies/consumer/batcher.rb +8 -9
  16. data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +3 -1
  17. data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +121 -0
  18. data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
  19. data/lib/chore/strategies/worker/helpers/ipc.rb +88 -0
  20. data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
  21. data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
  22. data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
  23. data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
  24. data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
  25. data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
  26. data/lib/chore/strategies/worker/single_worker_strategy.rb +35 -13
  27. data/lib/chore/unit_of_work.rb +8 -0
  28. data/lib/chore/util.rb +5 -1
  29. data/lib/chore/version.rb +3 -3
  30. data/lib/chore/worker.rb +29 -0
  31. data/spec/chore/cli_spec.rb +2 -2
  32. data/spec/chore/consumer_spec.rb +0 -4
  33. data/spec/chore/duplicate_detector_spec.rb +17 -5
  34. data/spec/chore/fetcher_spec.rb +0 -11
  35. data/spec/chore/manager_spec.rb +7 -0
  36. data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +71 -11
  37. data/spec/chore/queues/sqs/consumer_spec.rb +1 -3
  38. data/spec/chore/strategies/consumer/batcher_spec.rb +50 -0
  39. data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +1 -0
  40. data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
  41. data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +16 -1
  42. data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
  43. data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
  44. data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
  45. data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
  46. data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
  47. data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
  48. data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
  49. data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +25 -0
  50. data/spec/chore/worker_spec.rb +69 -1
  51. metadata +33 -5
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+ require 'chore/strategies/worker/helpers/work_distributor'
3
+
4
+ describe Chore::Strategy::WorkDistributor do
5
+ let(:timestamp) { Time.now }
6
+ let(:manager) { double('manager') }
7
+ let(:worker) { Chore::Strategy::WorkerInfo.new(1) }
8
+ let(:consumer) { double('consumer') }
9
+ let(:job) do
10
+ Chore::UnitOfWork.new(
11
+ SecureRandom.uuid,
12
+ 'test',
13
+ 60,
14
+ Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
15
+ 0
16
+ )
17
+ end
18
+ let(:socket) { double('socket') }
19
+
20
+ context '#include_ipc' do
21
+ it 'should include the Ipc module' do
22
+ expect(described_class.ipc_help).to eq(:available)
23
+ end
24
+ end
25
+
26
+ context '#fetch_and_assign_jobs' do
27
+ it 'should fetch jobs from the consumer' do
28
+ allow(described_class).to receive(:assign_jobs).and_return(true)
29
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
30
+ allow(manager).to receive(:return_work)
31
+ expect(manager).to receive(:fetch_work).with(1)
32
+ described_class.fetch_and_assign_jobs([worker], manager)
33
+ end
34
+
35
+ it 'should assign the fetched jobs to the workers' do
36
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
37
+ allow(manager).to receive(:return_work)
38
+ expect(described_class).to receive(:assign_jobs).with([job], [worker])
39
+ described_class.fetch_and_assign_jobs([worker], manager)
40
+ end
41
+
42
+ it 'should not return any work' do
43
+ allow(described_class).to receive(:assign_jobs).and_return([])
44
+ allow(manager).to receive(:fetch_work).with(1).and_return([job])
45
+ expect(manager).to receive(:return_work).with([])
46
+ described_class.fetch_and_assign_jobs([worker], manager)
47
+ end
48
+
49
+ it 'should raise and exception if it does not get an array from the manager' do
50
+ allow(manager).to receive(:fetch_work).with(1).and_return(nil)
51
+ allow(manager).to receive(:return_work)
52
+ expect { described_class.fetch_and_assign_jobs([worker], manager) }.to raise_error("DW: jobs needs to be a list got NilClass")
53
+ end
54
+
55
+ it 'should sleep if no jobs are available' do
56
+ expect(described_class).to receive(:sleep).with(0.1)
57
+ allow(manager).to receive(:fetch_work).with(1).and_return([])
58
+ allow(manager).to receive(:return_work)
59
+ described_class.fetch_and_assign_jobs([worker], manager)
60
+ end
61
+ end
62
+
63
+ context '#assign_jobs' do
64
+ it 'should raise an exception if we have no free workers' do
65
+ expect { described_class.send(:assign_jobs, [job], []) }.to raise_error('DW: assign_jobs got 0 workers')
66
+ end
67
+
68
+ it 'should remove the consumer object from the job object' do
69
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
70
+
71
+ described_class.send(:assign_jobs, [job], [worker])
72
+ end
73
+
74
+ it 'should raise an exception if more jobs than workers are provided to it' do
75
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
76
+
77
+ expect { described_class.send(:assign_jobs, [job, job], [worker]) }.to raise_error('DW: More Jobs than Sockets')
78
+ end
79
+
80
+ it 'should send the job object to the free worker' do
81
+ allow(described_class).to receive(:push_job_to_worker).and_return(true)
82
+
83
+ expect(described_class).to receive(:push_job_to_worker).with(job, worker)
84
+ described_class.send(:assign_jobs, [job], [worker])
85
+ end
86
+
87
+ it 'should return jobs that failed to be assigned' do
88
+ job2 = Chore::UnitOfWork.new(
89
+ SecureRandom.uuid,
90
+ 'test',
91
+ 60,
92
+ Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
93
+ 0
94
+ )
95
+ worker2 = Chore::Strategy::WorkerInfo.new(2)
96
+
97
+ allow(described_class).to receive(:push_job_to_worker).and_return(true, false)
98
+
99
+ expect(described_class).to receive(:push_job_to_worker).with(job, worker)
100
+ expect(described_class).to receive(:push_job_to_worker).with(job2, worker2)
101
+ unassigned_jobs = described_class.send(:assign_jobs, [job, job2], [worker, worker2])
102
+ expect(unassigned_jobs).to eq([job2])
103
+ end
104
+ end
105
+
106
+ context '#push_job_to_worker' do
107
+ before(:each) do
108
+ allow(described_class).to receive(:clear_ready).with(worker.socket).and_return(true)
109
+ allow(described_class).to receive(:send_msg).with(worker.socket, job).and_return(true)
110
+ end
111
+
112
+ it 'should clear all signals from the worker' do
113
+ expect(described_class).to receive(:clear_ready).with(worker.socket)
114
+ described_class.send(:push_job_to_worker, job, worker)
115
+ end
116
+
117
+ it 'should send the job as a message on the worker' do
118
+ expect(described_class).to receive(:send_msg).with(worker.socket, job)
119
+ described_class.send(:push_job_to_worker, job, worker)
120
+ end
121
+
122
+ it 'should return true if job successfully sent' do
123
+ expect(described_class.send(:push_job_to_worker, job, worker)).to eq(true)
124
+ end
125
+
126
+ it 'should return false if job failed to send' do
127
+ expect(described_class).to receive(:send_msg).and_raise(StandardError)
128
+ expect(described_class.send(:push_job_to_worker, job, worker)).to eq(false)
129
+ end
130
+ end
131
+ end
@@ -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