chore-core 1.5.10 → 1.7.2
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 +4 -4
- data/README.md +32 -6
- data/bin/chore +3 -2
- data/chore-core.gemspec +2 -2
- data/lib/chore/job.rb +20 -5
- data/lib/chore/queues/sqs.rb +31 -2
- data/lib/chore/queues/sqs/consumer.rb +13 -3
- data/lib/chore/tasks/queues.task +10 -3
- data/lib/chore/version.rb +2 -2
- data/lib/chore/worker.rb +24 -7
- data/spec/chore/duplicate_detector_spec.rb +13 -13
- data/spec/chore/job_spec.rb +26 -2
- data/spec/chore/manager_spec.rb +10 -14
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +12 -12
- data/spec/chore/queues/sqs/consumer_spec.rb +47 -36
- data/spec/chore/queues/sqs_spec.rb +30 -13
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +61 -58
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +6 -8
- data/spec/chore/worker_spec.rb +54 -1
- data/spec/chore_spec.rb +15 -15
- data/spec/spec_helper.rb +1 -1
- metadata +5 -5
data/spec/chore/manager_spec.rb
CHANGED
@@ -3,39 +3,35 @@ require 'timeout'
|
|
3
3
|
|
4
4
|
describe Chore::Manager do
|
5
5
|
|
6
|
-
let(:fetcher) {
|
6
|
+
let(:fetcher) { double(:start => nil) }
|
7
7
|
let(:opts) { { :num_workers => 4, :other_opt => 'hi', :fetcher => fetcher } }
|
8
8
|
|
9
9
|
before(:each) do
|
10
10
|
Chore.configure {|c| c.fetcher = fetcher; c.worker_strategy = Chore::Strategy::SingleWorkerStrategy }
|
11
|
-
fetcher.
|
11
|
+
expect(fetcher).to receive(:new).and_return(fetcher)
|
12
12
|
end
|
13
13
|
|
14
|
-
it 'should call create an instance of the defined fetcher' do
|
15
|
-
manager = Chore::Manager.new
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
14
|
describe 'running the manager' do
|
21
15
|
|
22
|
-
let(:manager) { Chore::Manager.new}
|
23
|
-
let(:work)
|
16
|
+
let(:manager) { Chore::Manager.new }
|
17
|
+
let(:work) do
|
18
|
+
Chore::UnitOfWork.new(Chore::Encoder::JsonEncoder.encode({ :class => 'MyClass', :args => [] }), double())
|
19
|
+
end
|
24
20
|
|
25
21
|
it 'should start the fetcher when starting the manager' do
|
26
|
-
fetcher.
|
22
|
+
expect(fetcher).to receive(:start)
|
27
23
|
manager.start
|
28
24
|
end
|
29
25
|
|
30
26
|
describe 'assigning messages' do
|
31
|
-
let(:worker) {
|
27
|
+
let(:worker) { double() }
|
32
28
|
|
33
29
|
before(:each) do
|
34
|
-
worker.
|
30
|
+
expect(worker).to receive(:start).with no_args
|
35
31
|
end
|
36
32
|
|
37
33
|
it 'should create a worker if one is available' do
|
38
|
-
Chore::Worker.
|
34
|
+
expect(Chore::Worker).to receive(:new).with(work,{}).and_return(worker)
|
39
35
|
manager.assign(work)
|
40
36
|
end
|
41
37
|
end
|
@@ -11,15 +11,15 @@ describe Chore::Queues::Filesystem::Consumer do
|
|
11
11
|
|
12
12
|
before do
|
13
13
|
Chore.config.fs_queue_root = test_queues_dir
|
14
|
-
Chore.config.
|
15
|
-
consumer.
|
14
|
+
expect(Chore.config).to receive(:default_queue_timeout).and_return(60)
|
15
|
+
allow(consumer).to receive(:sleep)
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
after do
|
19
19
|
FileUtils.rm_rf(test_queues_dir)
|
20
20
|
end
|
21
|
-
|
22
|
-
let!(:consumer_run_for_one_message) { consumer.
|
21
|
+
|
22
|
+
let!(:consumer_run_for_one_message) { expect(consumer).to receive(:running?).and_return(true, false) }
|
23
23
|
let(:test_job_hash) {{:class => "TestClass", :args => "test-args"}}
|
24
24
|
|
25
25
|
context "founding a published job" do
|
@@ -32,30 +32,30 @@ describe Chore::Queues::Filesystem::Consumer do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context "rejecting a job" do
|
35
|
-
let!(:consumer_run_for_two_messages) { consumer.
|
36
|
-
|
35
|
+
let!(:consumer_run_for_two_messages) { allow(consumer).to receive(:running?).and_return(true, false,true,false) }
|
36
|
+
|
37
37
|
it "should requeue a job that gets rejected" do
|
38
38
|
rejected = false
|
39
39
|
consumer.consume do |job_id, queue_name, job_hash|
|
40
40
|
consumer.reject(job_id)
|
41
41
|
rejected = true
|
42
42
|
end
|
43
|
-
rejected.
|
43
|
+
expect(rejected).to be true
|
44
44
|
|
45
45
|
expect { |b| consumer.consume(&b) }.to yield_with_args(anything, 'test-queue', 60, test_job_hash.to_json, 1)
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
context "completing a job" do
|
50
|
-
let!(:consumer_run_for_two_messages) { consumer.
|
51
|
-
|
50
|
+
let!(:consumer_run_for_two_messages) { allow(consumer).to receive(:running?).and_return(true, false,true,false) }
|
51
|
+
|
52
52
|
it "should remove job on completion" do
|
53
53
|
completed = false
|
54
54
|
consumer.consume do |job_id, queue_name, job_hash|
|
55
55
|
consumer.complete(job_id)
|
56
56
|
completed = true
|
57
57
|
end
|
58
|
-
completed.
|
58
|
+
expect(completed).to be true
|
59
59
|
|
60
60
|
expect { |b| consumer.consume(&b) }.to_not yield_control
|
61
61
|
end
|
@@ -11,27 +11,28 @@ describe Chore::Queues::SQS::Consumer do
|
|
11
11
|
let(:message_data) {{:id=>message.id, :queue=>message.queue.url, :visibility_timeout=>message.queue.visibility_timeout}}
|
12
12
|
let(:pool) { double("pool") }
|
13
13
|
let(:sqs) { double('AWS::SQS') }
|
14
|
+
let(:backoff_func) { nil }
|
14
15
|
|
15
16
|
before do
|
16
|
-
AWS::SQS.
|
17
|
-
sqs.
|
18
|
-
|
19
|
-
queues.
|
20
|
-
queues.
|
21
|
-
queue.
|
22
|
-
pool.
|
17
|
+
allow(AWS::SQS).to receive(:new).and_return(sqs)
|
18
|
+
allow(sqs).to receive(:queues) { queues }
|
19
|
+
|
20
|
+
allow(queues).to receive(:url_for) { queue_url }
|
21
|
+
allow(queues).to receive(:[]) { queue }
|
22
|
+
allow(queue).to receive(:receive_message) { message }
|
23
|
+
allow(pool).to receive(:empty!) { nil }
|
23
24
|
end
|
24
25
|
|
25
26
|
describe "consuming messages" do
|
26
|
-
let!(:consumer_run_for_one_message) { consumer.
|
27
|
-
let!(:messages_be_unique) { Chore::DuplicateDetector.
|
28
|
-
let!(:queue_contain_messages) { queue.
|
27
|
+
let!(:consumer_run_for_one_message) { allow(consumer).to receive(:running?).and_return(true, false) }
|
28
|
+
let!(:messages_be_unique) { allow_any_instance_of(Chore::DuplicateDetector).to receive(:found_duplicate?).and_return(false) }
|
29
|
+
let!(:queue_contain_messages) { allow(queue).to receive(:receive_messages).and_return(message) }
|
29
30
|
|
30
31
|
it 'should configure sqs' do
|
31
|
-
Chore.config.
|
32
|
-
Chore.config.
|
32
|
+
allow(Chore.config).to receive(:aws_access_key).and_return('key')
|
33
|
+
allow(Chore.config).to receive(:aws_secret_key).and_return('secret')
|
33
34
|
|
34
|
-
AWS::SQS.
|
35
|
+
expect(AWS::SQS).to receive(:new).with(
|
35
36
|
:access_key_id => 'key',
|
36
37
|
:secret_access_key => 'secret',
|
37
38
|
:logger => Chore.logger,
|
@@ -41,38 +42,38 @@ describe Chore::Queues::SQS::Consumer do
|
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'should not configure sqs multiple times' do
|
44
|
-
consumer.
|
45
|
+
allow(consumer).to receive(:running?).and_return(true, true, false)
|
45
46
|
|
46
|
-
AWS::SQS.
|
47
|
+
expect(AWS::SQS).to receive(:new).once.and_return(sqs)
|
47
48
|
consumer.consume
|
48
49
|
end
|
49
50
|
|
50
51
|
it 'should look up the queue url based on the queue name' do
|
51
|
-
queues.
|
52
|
+
expect(queues).to receive(:url_for).with('test').and_return(queue_url)
|
52
53
|
consumer.consume
|
53
54
|
end
|
54
55
|
|
55
56
|
it 'should look up the queue based on the queue url' do
|
56
|
-
queues.
|
57
|
+
expect(queues).to receive(:[]).with(queue_url).and_return(queue)
|
57
58
|
consumer.consume
|
58
59
|
end
|
59
60
|
|
60
61
|
context "should receive a message from the queue" do
|
61
62
|
|
62
63
|
it 'should use the default size of 10 when no queue_polling_size is specified' do
|
63
|
-
queue.
|
64
|
+
expect(queue).to receive(:receive_messages).with(:limit => 10, :attributes => [:receive_count])
|
64
65
|
consumer.consume
|
65
66
|
end
|
66
67
|
|
67
68
|
it 'should respect the queue_polling_size when specified' do
|
68
|
-
Chore.config.
|
69
|
-
queue.
|
69
|
+
allow(Chore.config).to receive(:queue_polling_size).and_return(5)
|
70
|
+
expect(queue).to receive(:receive_messages).with(:limit => 5, :attributes => [:receive_count])
|
70
71
|
consumer.consume
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
74
75
|
it "should check the uniqueness of the message" do
|
75
|
-
Chore::DuplicateDetector.
|
76
|
+
allow_any_instance_of(Chore::DuplicateDetector).to receive(:found_duplicate?).with(message_data).and_return(false)
|
76
77
|
consumer.consume
|
77
78
|
end
|
78
79
|
|
@@ -81,55 +82,65 @@ describe Chore::Queues::SQS::Consumer do
|
|
81
82
|
end
|
82
83
|
|
83
84
|
it 'should not yield for a dupe message' do
|
84
|
-
Chore::DuplicateDetector.
|
85
|
+
allow_any_instance_of(Chore::DuplicateDetector).to receive(:found_duplicate?).with(message_data).and_return(true)
|
85
86
|
expect {|b| consumer.consume(&b) }.not_to yield_control
|
86
87
|
end
|
87
88
|
|
88
89
|
context 'with no messages' do
|
89
|
-
let!(:consumer_run_for_one_message) { consumer.
|
90
|
-
let!(:queue_contain_messages) { queue.
|
90
|
+
let!(:consumer_run_for_one_message) { allow(consumer).to receive(:running?).and_return(true, true, false) }
|
91
|
+
let!(:queue_contain_messages) { allow(queue).to receive(:receive_messages).and_return(message, nil) }
|
91
92
|
|
92
93
|
it 'should sleep' do
|
93
|
-
consumer.
|
94
|
+
expect(consumer).to receive(:sleep).with(1)
|
94
95
|
consumer.consume
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
99
|
context 'with messages' do
|
99
|
-
let!(:consumer_run_for_one_message) { consumer.
|
100
|
-
let!(:queue_contain_messages) { queue.
|
100
|
+
let!(:consumer_run_for_one_message) { allow(consumer).to receive(:running?).and_return(true, true, false) }
|
101
|
+
let!(:queue_contain_messages) { allow(queue).to receive(:receive_messages).and_return(message, message) }
|
101
102
|
|
102
103
|
it 'should not sleep' do
|
103
|
-
consumer.
|
104
|
+
expect(consumer).to_not receive(:sleep)
|
104
105
|
consumer.consume
|
105
106
|
end
|
106
107
|
end
|
107
108
|
end
|
108
109
|
|
110
|
+
describe '#delay' do
|
111
|
+
let(:item) { Chore::UnitOfWork.new(message.id, message.queue, 60, message.body, 0, consumer) }
|
112
|
+
let(:backoff_func) { lambda { |item| 2 } }
|
113
|
+
|
114
|
+
it 'changes the visiblity of the message' do
|
115
|
+
expect(queue).to receive(:batch_change_visibility).with(2, [item.id])
|
116
|
+
consumer.delay(item, backoff_func)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
109
120
|
describe '#reset_connection!' do
|
110
121
|
it 'should reset the connection after a call to reset_connection!' do
|
111
|
-
AWS::Core::Http::ConnectionPool.
|
112
|
-
pool.
|
122
|
+
expect(AWS::Core::Http::ConnectionPool).to receive(:pools).and_return([pool])
|
123
|
+
expect(pool).to receive(:empty!)
|
113
124
|
Chore::Queues::SQS::Consumer.reset_connection!
|
114
125
|
consumer.send(:queue)
|
115
126
|
end
|
116
127
|
|
117
128
|
it 'should not reset the connection between calls' do
|
118
129
|
sqs = consumer.send(:queue)
|
119
|
-
sqs.
|
130
|
+
expect(sqs).to be consumer.send(:queue)
|
120
131
|
end
|
121
132
|
|
122
133
|
it 'should reconfigure sqs' do
|
123
|
-
consumer.
|
124
|
-
Chore::DuplicateDetector.
|
134
|
+
allow(consumer).to receive(:running?).and_return(true, false)
|
135
|
+
allow_any_instance_of(Chore::DuplicateDetector).to receive(:found_duplicate?).and_return(false)
|
125
136
|
|
126
|
-
queue.
|
137
|
+
allow(queue).to receive(:receive_messages).and_return(message)
|
127
138
|
consumer.consume
|
128
139
|
|
129
140
|
Chore::Queues::SQS::Consumer.reset_connection!
|
130
|
-
AWS::SQS.
|
141
|
+
allow(AWS::SQS).to receive(:new).and_return(sqs)
|
131
142
|
|
132
|
-
consumer.
|
143
|
+
expect(consumer).to receive(:running?).and_return(true, false)
|
133
144
|
consumer.consume
|
134
145
|
end
|
135
146
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
1
3
|
module Chore
|
2
4
|
describe Queues::SQS do
|
3
5
|
context "when managing queues" do
|
@@ -8,30 +10,45 @@ module Chore
|
|
8
10
|
let(:fake_queue) {double(Object)}
|
9
11
|
|
10
12
|
before(:each) do
|
11
|
-
AWS::SQS.
|
12
|
-
Chore.
|
13
|
-
fake_queue.
|
14
|
-
|
15
|
-
fake_queue_collection.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
fake_sqs.stub(:queues).and_return(fake_queue_collection)
|
21
|
-
fake_queue_collection.stub(:url_for).with(queue_name).and_return(queue_url)
|
13
|
+
allow(AWS::SQS).to receive(:new).and_return(fake_sqs)
|
14
|
+
allow(Chore).to receive(:prefixed_queue_names).and_return([queue_name])
|
15
|
+
allow(fake_queue).to receive(:delete)
|
16
|
+
|
17
|
+
allow(fake_queue_collection).to receive(:[]).and_return(fake_queue)
|
18
|
+
allow(fake_queue_collection).to receive(:create)
|
19
|
+
allow(fake_queue_collection).to receive(:url_for).with(queue_name).and_return(queue_url)
|
20
|
+
|
21
|
+
allow(fake_sqs).to receive(:queues).and_return(fake_queue_collection)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'should create queues that are defined in its internal job name list' do
|
25
25
|
#Only one job defined in the spec suite
|
26
|
-
fake_queue_collection.
|
26
|
+
expect(fake_queue_collection).to receive(:create)
|
27
27
|
Chore::Queues::SQS.create_queues!
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'should delete queues that are defined in its internal job name list' do
|
31
31
|
#Only one job defined in the spec suite
|
32
|
-
fake_queue.
|
32
|
+
expect(fake_queue).to receive(:delete)
|
33
33
|
Chore::Queues::SQS.delete_queues!
|
34
34
|
end
|
35
|
+
|
36
|
+
context 'and checking for existing queues' do
|
37
|
+
it 'checks for existing queues' do
|
38
|
+
expect(described_class).to receive(:existing_queues).and_return([])
|
39
|
+
Chore::Queues::SQS.create_queues!(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an error if a queue does exist' do
|
43
|
+
allow(described_class).to receive(:existing_queues).and_return([queue_name])
|
44
|
+
expect{Chore::Queues::SQS.create_queues!(true)}.to raise_error(RuntimeError)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not raise an error if a queue does not exist' do
|
48
|
+
allow(described_class).to receive(:existing_queues).and_return([])
|
49
|
+
expect{Chore::Queues::SQS.create_queues!(true)}.not_to raise_error
|
50
|
+
end
|
51
|
+
end
|
35
52
|
end
|
36
53
|
end
|
37
54
|
end
|
@@ -5,7 +5,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
5
5
|
let(:manager) { double('manager') }
|
6
6
|
let(:forker) do
|
7
7
|
strategy = Chore::Strategy::ForkedWorkerStrategy.new(manager)
|
8
|
-
strategy.
|
8
|
+
allow(strategy).to receive(:exit!)
|
9
9
|
strategy
|
10
10
|
end
|
11
11
|
let(:job_timeout) { 60 }
|
@@ -14,72 +14,73 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
14
14
|
let(:pid) { Random.rand(2048) }
|
15
15
|
|
16
16
|
after(:each) do
|
17
|
-
Process.
|
17
|
+
allow(Process).to receive(:kill) { nil }
|
18
|
+
allow(Process).to receive(:wait) { pid }
|
18
19
|
forker.stop!
|
19
20
|
end
|
20
21
|
|
21
22
|
context "signal handling" do
|
22
23
|
it 'should trap signals from terminating children and reap them' do
|
23
|
-
Chore::Signal.
|
24
|
-
Chore::Strategy::ForkedWorkerStrategy.
|
24
|
+
expect(Chore::Signal).to receive(:trap).with('CHLD').and_yield
|
25
|
+
allow_any_instance_of(Chore::Strategy::ForkedWorkerStrategy).to receive(:reap_terminated_workers!)
|
25
26
|
forker
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
30
|
context '#assign' do
|
30
31
|
before(:each) do
|
31
|
-
forker.
|
32
|
-
forker.
|
32
|
+
allow(forker).to receive(:fork).and_yield.and_return(pid, pid + 1)
|
33
|
+
allow(forker).to receive(:after_fork)
|
33
34
|
end
|
34
35
|
after(:each) do
|
35
36
|
Chore.clear_hooks!
|
36
37
|
end
|
37
38
|
|
38
39
|
it 'should pop off the worker queue when assignd a job' do
|
39
|
-
Queue.
|
40
|
+
allow_any_instance_of(Queue).to receive(:pop)
|
40
41
|
forker.assign(job)
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'should assign a job to a new worker' do
|
44
|
-
Chore::Worker.
|
45
|
-
worker.
|
45
|
+
expect(Chore::Worker).to receive(:new).with(job, {}).and_return(worker)
|
46
|
+
expect(worker).to receive(:start)
|
46
47
|
forker.assign(job)
|
47
48
|
end
|
48
49
|
|
49
50
|
it 'should add an assigned worker to the worker list' do
|
50
|
-
forker.workers.
|
51
|
+
expect(forker.workers).to receive(:[]=).with(pid,kind_of(Chore::Worker))
|
51
52
|
forker.assign(job)
|
52
53
|
end
|
53
54
|
|
54
55
|
it 'should fork a child for each new worker' do
|
55
|
-
forker.
|
56
|
+
expect(forker).to receive(:fork).and_yield.and_return(pid)
|
56
57
|
forker.assign(job)
|
57
58
|
end
|
58
59
|
|
59
60
|
it 'should remove the worker from the list when it has completed' do
|
60
61
|
forker.assign(job)
|
61
62
|
|
62
|
-
Process.
|
63
|
+
expect(Process).to receive(:wait).with(pid, Process::WNOHANG).and_return(pid)
|
63
64
|
forker.send(:reap_terminated_workers!)
|
64
65
|
|
65
|
-
forker.workers.
|
66
|
+
expect(forker.workers).to_not include(pid)
|
66
67
|
end
|
67
68
|
|
68
69
|
it 'should not remove the worker from the list if it has not yet completed' do
|
69
70
|
forker.assign(job)
|
70
71
|
|
71
|
-
Process.
|
72
|
+
expect(Process).to receive(:wait).and_return(nil)
|
72
73
|
forker.send(:reap_terminated_workers!)
|
73
74
|
|
74
|
-
forker.workers.
|
75
|
+
expect(forker.workers).to include(pid)
|
75
76
|
end
|
76
77
|
|
77
78
|
it 'should add the worker back to the queue when it has completed' do
|
78
79
|
2.times { forker.assign(job) }
|
79
80
|
|
80
|
-
Queue.
|
81
|
+
allow_any_instance_of(Queue).to receive(:<<).with(:worker)
|
81
82
|
|
82
|
-
Process.
|
83
|
+
allow(Process).to receive(:wait).and_return(pid, pid + 1)
|
83
84
|
forker.send(:reap_terminated_workers!)
|
84
85
|
end
|
85
86
|
|
@@ -87,9 +88,9 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
87
88
|
forker.assign(job)
|
88
89
|
reaped = false
|
89
90
|
|
90
|
-
forker.
|
91
|
+
expect(forker).to receive(:release_worker).once
|
91
92
|
|
92
|
-
|
93
|
+
wait_proc = Proc.new do
|
93
94
|
if !reaped
|
94
95
|
reaped = true
|
95
96
|
forker.send(:reap_terminated_workers!)
|
@@ -97,17 +98,19 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
97
98
|
|
98
99
|
pid
|
99
100
|
end
|
101
|
+
|
102
|
+
expect(Process).to receive(:wait).with(pid, anything).and_return(wait_proc)
|
100
103
|
forker.send(:reap_terminated_workers!)
|
101
104
|
end
|
102
105
|
|
103
106
|
it 'should continue to allow reaping after an exception occurs' do
|
104
107
|
2.times { forker.assign(job) }
|
105
108
|
|
106
|
-
Process.
|
107
|
-
Process.
|
109
|
+
expect(Process).to receive(:wait).and_raise(Errno::ECHILD)
|
110
|
+
expect(Process).to receive(:wait).and_return(pid + 1)
|
108
111
|
forker.send(:reap_terminated_workers!)
|
109
112
|
|
110
|
-
forker.workers.
|
113
|
+
expect(forker.workers).to eq({})
|
111
114
|
end
|
112
115
|
|
113
116
|
[:before_fork, :after_fork, :within_fork, :before_fork_shutdown].each do |hook|
|
@@ -115,7 +118,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
115
118
|
hook_called = false
|
116
119
|
Chore.add_hook(hook) { hook_called = true }
|
117
120
|
forker.assign(job)
|
118
|
-
hook_called.
|
121
|
+
expect(hook_called).to be true
|
119
122
|
end
|
120
123
|
end
|
121
124
|
|
@@ -123,26 +126,26 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
123
126
|
hook_called = false
|
124
127
|
Chore.add_hook(:around_fork) {|&blk| hook_called = true; blk.call }
|
125
128
|
forker.assign(job)
|
126
|
-
hook_called.
|
129
|
+
expect(hook_called).to be true
|
127
130
|
end
|
128
131
|
|
129
132
|
it 'should run before_fork_shutdown hooks even if job errors' do
|
130
|
-
Chore::Worker.
|
131
|
-
worker.
|
133
|
+
expect(Chore::Worker).to receive(:new).and_return(worker)
|
134
|
+
expect(worker).to receive(:start).and_raise(ArgumentError)
|
132
135
|
|
133
136
|
hook_called = false
|
134
137
|
Chore.add_hook(:before_fork_shutdown) { hook_called = true }
|
135
138
|
|
136
139
|
begin
|
137
140
|
forker.assign(job)
|
138
|
-
rescue ArgumentError
|
141
|
+
rescue ArgumentError
|
139
142
|
end
|
140
143
|
|
141
|
-
hook_called.
|
144
|
+
expect(hook_called).to be true
|
142
145
|
end
|
143
146
|
|
144
147
|
it 'should exit the process without running at_exit handlers' do
|
145
|
-
forker.
|
148
|
+
expect(forker).to receive(:exit!).with(true)
|
146
149
|
forker.assign(job)
|
147
150
|
end
|
148
151
|
|
@@ -150,19 +153,19 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
150
153
|
let(:job_timeout) { 0.1 }
|
151
154
|
|
152
155
|
before(:each) do
|
153
|
-
Process.
|
154
|
-
Chore::Worker.
|
156
|
+
allow(Process).to receive(:kill)
|
157
|
+
expect(Chore::Worker).to receive(:new).and_return(worker)
|
155
158
|
end
|
156
159
|
|
157
160
|
it 'should kill the process if it expires' do
|
158
|
-
Process.
|
161
|
+
expect(Process).to receive(:kill).with('KILL', pid)
|
159
162
|
forker.assign(job)
|
160
163
|
sleep 2
|
161
164
|
end
|
162
165
|
|
163
166
|
it 'should run the on_failure callback hook' do
|
164
167
|
forker.assign(job)
|
165
|
-
Chore.
|
168
|
+
expect(Chore).to receive(:run_hooks_for).with(:on_failure, anything, instance_of(Chore::TimeoutError))
|
166
169
|
sleep 2
|
167
170
|
end
|
168
171
|
end
|
@@ -171,14 +174,14 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
171
174
|
let(:job_timeout) { 0.1 }
|
172
175
|
|
173
176
|
before(:each) do
|
174
|
-
Chore::Worker.
|
177
|
+
expect(Chore::Worker).to receive(:new).and_return(worker)
|
175
178
|
end
|
176
179
|
|
177
180
|
it 'should not kill the process if does not expire' do
|
178
|
-
Process.
|
181
|
+
expect(Process).to_not receive(:kill)
|
179
182
|
|
180
183
|
forker.assign(job)
|
181
|
-
Process.
|
184
|
+
expect(Process).to receive(:wait).and_return(pid)
|
182
185
|
forker.send(:reap_terminated_workers!)
|
183
186
|
sleep 2
|
184
187
|
end
|
@@ -187,7 +190,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
187
190
|
|
188
191
|
context '#before_fork' do
|
189
192
|
before(:each) do
|
190
|
-
Chore::Worker.
|
193
|
+
expect(Chore::Worker).to receive(:new).and_return(worker)
|
191
194
|
end
|
192
195
|
after(:each) do
|
193
196
|
Chore.clear_hooks!
|
@@ -195,14 +198,14 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
195
198
|
|
196
199
|
it 'should release the worker if an exception occurs' do
|
197
200
|
Chore.add_hook(:before_fork) { raise ArgumentError }
|
198
|
-
forker.
|
201
|
+
expect(forker).to receive(:release_worker)
|
199
202
|
forker.assign(job)
|
200
203
|
end
|
201
204
|
end
|
202
205
|
|
203
206
|
context '#around_fork' do
|
204
207
|
before(:each) do
|
205
|
-
Chore::Worker.
|
208
|
+
expect(Chore::Worker).to receive(:new).and_return(worker)
|
206
209
|
end
|
207
210
|
after(:each) do
|
208
211
|
Chore.clear_hooks!
|
@@ -210,7 +213,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
210
213
|
|
211
214
|
it 'should release the worker if an exception occurs' do
|
212
215
|
Chore.add_hook(:around_fork) {|worker, &block| raise ArgumentError}
|
213
|
-
forker.
|
216
|
+
expect(forker).to receive(:release_worker)
|
214
217
|
forker.assign(job)
|
215
218
|
end
|
216
219
|
end
|
@@ -219,61 +222,61 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
219
222
|
let(:worker) { double('worker') }
|
220
223
|
|
221
224
|
it 'should clear signals' do
|
222
|
-
forker.
|
225
|
+
expect(forker).to receive(:clear_child_signals)
|
223
226
|
forker.send(:after_fork,worker)
|
224
227
|
end
|
225
228
|
|
226
229
|
it 'should trap signals' do
|
227
|
-
forker.
|
230
|
+
expect(forker).to receive(:trap_child_signals)
|
228
231
|
forker.send(:after_fork,worker)
|
229
232
|
end
|
230
233
|
|
231
234
|
it 'should set the procline' do
|
232
|
-
forker.
|
235
|
+
expect(forker).to receive(:procline)
|
233
236
|
forker.send(:after_fork,worker)
|
234
237
|
end
|
235
238
|
end
|
236
239
|
|
237
240
|
context '#stop!' do
|
238
241
|
before(:each) do
|
239
|
-
Process.
|
242
|
+
allow(Process).to receive(:kill)
|
240
243
|
|
241
|
-
forker.
|
242
|
-
forker.
|
244
|
+
expect(forker).to receive(:fork).and_yield.and_return(pid)
|
245
|
+
expect(forker).to receive(:after_fork)
|
243
246
|
forker.assign(job)
|
244
247
|
end
|
245
248
|
|
246
249
|
it 'should send a quit signal to each child' do
|
247
|
-
Process.
|
248
|
-
Process.
|
250
|
+
expect(Process).to receive(:kill).once.with('QUIT', pid)
|
251
|
+
allow(Process).to receive(:wait).and_return(pid, nil)
|
249
252
|
forker.stop!
|
250
253
|
end
|
251
254
|
|
252
255
|
it 'should reap each worker' do
|
253
|
-
Process.
|
256
|
+
expect(Process).to receive(:wait).and_return(pid)
|
254
257
|
forker.stop!
|
255
|
-
forker.workers.
|
258
|
+
expect(forker.workers).to eq({})
|
256
259
|
end
|
257
260
|
|
258
261
|
it 'should resend quit signal to children if workers are not reaped' do
|
259
|
-
Process.
|
260
|
-
Process.
|
262
|
+
expect(Process).to receive(:kill).with('QUIT', pid)
|
263
|
+
allow(Process).to receive(:wait).and_return(nil, pid, nil)
|
261
264
|
forker.stop!
|
262
265
|
end
|
263
266
|
|
264
267
|
it 'should send kill signal to children if timeout is exceeded' do
|
265
|
-
Chore.config.
|
266
|
-
Process.
|
267
|
-
Process.
|
268
|
-
Process.
|
268
|
+
expect(Chore.config).to receive(:shutdown_timeout).and_return(0.05)
|
269
|
+
expect(Process).to receive(:kill).once.with('QUIT', pid)
|
270
|
+
expect(Process).to receive(:wait).and_return(nil)
|
271
|
+
expect(Process).to receive(:kill).once.with('KILL', pid)
|
269
272
|
forker.stop!
|
270
273
|
end
|
271
274
|
|
272
275
|
it 'should not allow more work to be assigned' do
|
273
|
-
Process.
|
276
|
+
allow(Process).to receive(:wait).and_return(pid, nil)
|
274
277
|
forker.stop!
|
275
278
|
|
276
|
-
Chore::Worker.
|
279
|
+
expect(Chore::Worker).to_not receive(:new)
|
277
280
|
forker.assign(job)
|
278
281
|
end
|
279
282
|
end
|