chore-core 1.5.10 → 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|