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.
- checksums.yaml +5 -5
- data/README.md +6 -0
- data/chore-core.gemspec +1 -0
- data/lib/chore.rb +11 -5
- data/lib/chore/cli.rb +21 -2
- data/lib/chore/consumer.rb +15 -5
- data/lib/chore/fetcher.rb +12 -7
- data/lib/chore/hooks.rb +2 -1
- data/lib/chore/job.rb +17 -0
- data/lib/chore/manager.rb +18 -2
- data/lib/chore/queues/filesystem/consumer.rb +116 -59
- data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
- data/lib/chore/queues/filesystem/publisher.rb +12 -18
- data/lib/chore/queues/sqs/consumer.rb +6 -21
- data/lib/chore/strategies/consumer/batcher.rb +8 -9
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +3 -1
- data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +121 -0
- data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
- data/lib/chore/strategies/worker/helpers/ipc.rb +88 -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/strategies/worker/single_worker_strategy.rb +35 -13
- data/lib/chore/unit_of_work.rb +8 -0
- data/lib/chore/util.rb +5 -1
- data/lib/chore/version.rb +3 -3
- data/lib/chore/worker.rb +29 -0
- data/spec/chore/cli_spec.rb +2 -2
- data/spec/chore/consumer_spec.rb +0 -4
- 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 +71 -11
- data/spec/chore/queues/sqs/consumer_spec.rb +1 -3
- data/spec/chore/strategies/consumer/batcher_spec.rb +50 -0
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +1 -0
- data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +16 -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 +25 -0
- data/spec/chore/worker_spec.rb +69 -1
- 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
|