chore-core 1.8.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +173 -150
- data/chore-core.gemspec +3 -3
- data/lib/chore.rb +31 -5
- data/lib/chore/cli.rb +22 -4
- data/lib/chore/configuration.rb +1 -1
- data/lib/chore/consumer.rb +54 -12
- data/lib/chore/fetcher.rb +12 -7
- data/lib/chore/hooks.rb +2 -1
- data/lib/chore/job.rb +19 -0
- data/lib/chore/manager.rb +18 -2
- data/lib/chore/publisher.rb +18 -2
- data/lib/chore/queues/filesystem/consumer.rb +126 -64
- data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
- data/lib/chore/queues/filesystem/publisher.rb +13 -19
- data/lib/chore/queues/sqs.rb +22 -13
- data/lib/chore/queues/sqs/consumer.rb +64 -51
- data/lib/chore/queues/sqs/publisher.rb +26 -17
- data/lib/chore/strategies/consumer/batcher.rb +14 -15
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +9 -7
- data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +120 -0
- data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
- data/lib/chore/strategies/worker/helpers/ipc.rb +87 -0
- data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
- data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
- data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
- data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
- data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
- data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
- data/lib/chore/strategies/worker/single_worker_strategy.rb +35 -13
- data/lib/chore/unit_of_work.rb +10 -1
- data/lib/chore/util.rb +5 -1
- data/lib/chore/version.rb +3 -3
- data/lib/chore/worker.rb +32 -3
- data/spec/chore/cli_spec.rb +2 -2
- data/spec/chore/consumer_spec.rb +1 -5
- data/spec/chore/duplicate_detector_spec.rb +17 -5
- data/spec/chore/fetcher_spec.rb +0 -11
- data/spec/chore/manager_spec.rb +7 -0
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +74 -16
- data/spec/chore/queues/sqs/consumer_spec.rb +117 -78
- data/spec/chore/queues/sqs/publisher_spec.rb +49 -60
- data/spec/chore/queues/sqs_spec.rb +32 -41
- data/spec/chore/strategies/consumer/batcher_spec.rb +50 -0
- data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +3 -3
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +7 -6
- data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +17 -2
- 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 +82 -14
- data/spec/spec_helper.rb +1 -1
- data/spec/support/queues/sqs/fake_objects.rb +18 -0
- metadata +39 -15
@@ -1,53 +1,44 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
allow(fake_queue).to receive(:delete)
|
3
|
+
describe Chore::Queues::SQS do
|
4
|
+
include_context 'fake objects'
|
5
|
+
|
6
|
+
context "when managing queues" do
|
7
|
+
before(:each) do
|
8
|
+
allow(Aws::SQS::Client).to receive(:new).and_return(sqs)
|
9
|
+
allow(sqs).to receive(:create_queue).and_return(queue)
|
10
|
+
allow(sqs).to receive(:delete_queue).and_return(Struct.new(nil))
|
11
|
+
allow(queue).to receive(:delete).and_return(sqs.delete_queue(queue))
|
12
|
+
allow(Chore).to receive(:prefixed_queue_names).and_return([queue_name])
|
13
|
+
allow(queue).to receive(:delete)
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
it 'should create queues that are defined in its internal job name list' do
|
17
|
+
#Only one job defined in the spec suite
|
18
|
+
expect(sqs).to receive(:create_queue).with(queue_name: queue_name)
|
19
|
+
Chore::Queues::SQS.create_queues!
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
22
|
+
it 'should delete queues that are defined in its internal job name list' do
|
23
|
+
#Only one job defined in the spec suite
|
24
|
+
expect(sqs).to receive(:delete_queue).with(queue_url: sqs.get_queue_url.queue_url)
|
25
|
+
Chore::Queues::SQS.delete_queues!
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
expect(
|
27
|
-
Chore::Queues::SQS.create_queues!
|
28
|
+
context 'and checking for existing queues' do
|
29
|
+
it 'checks for existing queues' do
|
30
|
+
expect(described_class).to receive(:existing_queues).and_return([])
|
31
|
+
Chore::Queues::SQS.create_queues!(true)
|
28
32
|
end
|
29
33
|
|
30
|
-
it '
|
31
|
-
|
32
|
-
expect(
|
33
|
-
Chore::Queues::SQS.delete_queues!
|
34
|
+
it 'raises an error if a queue does exist' do
|
35
|
+
allow(described_class).to receive(:existing_queues).and_return([queue_name])
|
36
|
+
expect{Chore::Queues::SQS.create_queues!(true)}.to raise_error(RuntimeError)
|
34
37
|
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
39
|
+
it 'does not raise an error if a queue does not exist' do
|
40
|
+
allow(described_class).to receive(:existing_queues).and_return([])
|
41
|
+
expect{Chore::Queues::SQS.create_queues!(true)}.not_to raise_error
|
51
42
|
end
|
52
43
|
end
|
53
44
|
end
|
@@ -90,4 +90,54 @@ describe Chore::Strategy::Batcher do
|
|
90
90
|
subject.batch.should == ['test']
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
describe 'schedule' do
|
95
|
+
let(:timeout) { 5 }
|
96
|
+
let(:batch) { [] }
|
97
|
+
|
98
|
+
before(:each) do
|
99
|
+
Thread.stub(:new) do |&block|
|
100
|
+
# Stop the batcher on the next iteration
|
101
|
+
subject.stub(:sleep) { subject.stop }
|
102
|
+
|
103
|
+
# Run the scheduling thread
|
104
|
+
block.call(timeout)
|
105
|
+
end
|
106
|
+
|
107
|
+
subject.batch = batch.dup
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'with no items' do
|
111
|
+
it 'should not invoke the callback' do
|
112
|
+
callback.should_not_receive(:call)
|
113
|
+
subject.schedule(timeout)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'with new items' do
|
118
|
+
let(:batch) do
|
119
|
+
[
|
120
|
+
Chore::UnitOfWork.new.tap {|work| work.created_at = Time.now - 2}
|
121
|
+
]
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should not invoke the callback' do
|
125
|
+
callback.should_not_receive(:call).with(batch)
|
126
|
+
subject.schedule(timeout)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with old items' do
|
131
|
+
let(:batch) do
|
132
|
+
[
|
133
|
+
Chore::UnitOfWork.new.tap {|work| work.created_at = Time.now - 6}
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should invoke the callback' do
|
138
|
+
callback.should_receive(:call).with(batch)
|
139
|
+
subject.schedule(timeout)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
93
143
|
end
|
@@ -11,13 +11,13 @@ describe Chore::Strategy::SingleConsumerStrategy do
|
|
11
11
|
fetcher.stub(:manager) { manager }
|
12
12
|
Chore.config.stub(:queues).and_return(test_queues)
|
13
13
|
Chore.config.stub(:consumer).and_return(consumer)
|
14
|
-
|
14
|
+
|
15
15
|
end
|
16
16
|
|
17
17
|
it "should consume and then assign a message" do
|
18
18
|
consumer.should_receive(:new).with(test_queues.first).and_return(consumer)
|
19
|
-
consumer.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
|
20
|
-
manager.should_receive(:assign).with(Chore::UnitOfWork.new(1, 'test-queue', 60, "test", 0, consumer))
|
19
|
+
consumer.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
|
20
|
+
manager.should_receive(:assign).with(Chore::UnitOfWork.new(1, nil, 'test-queue', 60, "test", 0, consumer))
|
21
21
|
strategy.fetch
|
22
22
|
end
|
23
23
|
end
|
@@ -29,8 +29,8 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
|
|
29
29
|
before(:each) do
|
30
30
|
fetcher.stub(:consumers) { [consumer] }
|
31
31
|
fetcher.stub(:manager) { manager }
|
32
|
-
Chore.configure do |c|
|
33
|
-
c.queues = ['test']
|
32
|
+
Chore.configure do |c|
|
33
|
+
c.queues = ['test']
|
34
34
|
c.consumer = consumer
|
35
35
|
c.batch_size = batch_size
|
36
36
|
end
|
@@ -40,7 +40,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
|
|
40
40
|
let(:batch_size) { 2 }
|
41
41
|
|
42
42
|
it "should queue but not assign the message" do
|
43
|
-
consumer.any_instance.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
|
43
|
+
consumer.any_instance.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
|
44
44
|
strategy.fetch
|
45
45
|
strategy.batcher.batch.size.should == 1
|
46
46
|
|
@@ -51,6 +51,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
|
|
51
51
|
work.message.should == "test"
|
52
52
|
work.previous_attempts.should == 0
|
53
53
|
work.current_attempt.should == 1
|
54
|
+
work.created_at.should_not be_nil
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -59,7 +60,7 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
|
|
59
60
|
|
60
61
|
it "should assign the batch" do
|
61
62
|
manager.should_receive(:assign)
|
62
|
-
consumer.any_instance.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
|
63
|
+
consumer.any_instance.should_receive(:consume).and_yield(1, nil, 'test-queue', 60, "test", 0)
|
63
64
|
strategy.fetch
|
64
65
|
strategy.batcher.batch.size.should == 0
|
65
66
|
end
|
@@ -89,8 +90,8 @@ describe Chore::Strategy::ThreadedConsumerStrategy do
|
|
89
90
|
|
90
91
|
before do
|
91
92
|
fetcher.stub(:consumers) { [bad_consumer] }
|
92
|
-
Chore.configure do |c|
|
93
|
-
c.queues = ['test']
|
93
|
+
Chore.configure do |c|
|
94
|
+
c.queues = ['test']
|
94
95
|
c.consumer = bad_consumer
|
95
96
|
c.batch_size = batch_size
|
96
97
|
c.threads_per_queue = 1
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestConsumer < Chore::Consumer
|
4
|
+
def initialize(queue_name, opts={})
|
5
|
+
end
|
6
|
+
|
7
|
+
def consume
|
8
|
+
# just something that looks like an SQS message
|
9
|
+
msg = OpenStruct.new( :id => 1, :body => "test" )
|
10
|
+
yield msg if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NoQueueConsumer < Chore::Consumer
|
15
|
+
def initialize(queue_name, opts={})
|
16
|
+
raise Chore::TerribleMistake
|
17
|
+
end
|
18
|
+
|
19
|
+
def consume
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Chore::Strategy::ThrottledConsumerStrategy do
|
24
|
+
let(:fetcher) { double("fetcher") }
|
25
|
+
let(:manager) { double("manager") }
|
26
|
+
let(:thread) {double("thread")}
|
27
|
+
let(:consume_queue) { "TestQueue" }
|
28
|
+
let(:consumer) { TestConsumer }
|
29
|
+
let(:consumer_object) { consumer.new(consume_queue) }
|
30
|
+
let(:strategy) { Chore::Strategy::ThrottledConsumerStrategy.new(fetcher) }
|
31
|
+
let(:config) { double("config") }
|
32
|
+
let(:sized_queue) {double("sized_queue")}
|
33
|
+
let(:return_queue) {double("return_queue")}
|
34
|
+
let(:work) { double("work") }
|
35
|
+
let(:msg) { OpenStruct.new( :id => 1, :body => "test" ) }
|
36
|
+
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
allow(fetcher).to receive(:consumers).and_return([consumer])
|
40
|
+
allow(fetcher).to receive(:manager).and_return(manager)
|
41
|
+
Chore.configure do |c|
|
42
|
+
c.queues = [consume_queue]
|
43
|
+
c.consumer = consumer
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context '#fetch' do
|
48
|
+
it 'should call consume, \'@number_of_consumers\' number of times' do
|
49
|
+
allow(strategy).to receive(:consume).and_return(thread)
|
50
|
+
allow(thread).to receive(:join).and_return(true)
|
51
|
+
allow(Chore).to receive(:config).and_return(config)
|
52
|
+
allow(config).to receive(:queues).and_return([consume_queue])
|
53
|
+
strategy.instance_variable_set(:@consumers_per_queue, 5)
|
54
|
+
expect(strategy).to receive(:consume).with(consume_queue).exactly(5).times
|
55
|
+
strategy.fetch
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context '#stop!' do
|
60
|
+
it 'should should stop itself, and every other consumer' do
|
61
|
+
allow(strategy).to receive(:running?).and_return(true)
|
62
|
+
strategy.instance_eval('@running = true')
|
63
|
+
strategy.instance_variable_set(:@consumers, [consumer_object])
|
64
|
+
expect(consumer_object).to receive(:stop)
|
65
|
+
strategy.stop!
|
66
|
+
expect(strategy.instance_variable_get(:@running)).to eq(false)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context '#provide_work' do
|
71
|
+
it 'should return upto n units of work' do
|
72
|
+
n = 2
|
73
|
+
strategy.instance_variable_set(:@queue, sized_queue)
|
74
|
+
allow(sized_queue).to receive(:size).and_return(10)
|
75
|
+
allow(sized_queue).to receive(:pop).and_return(work)
|
76
|
+
expect(sized_queue).to receive(:pop).exactly(n).times
|
77
|
+
res = strategy.provide_work(n)
|
78
|
+
expect(res.size).to eq(n)
|
79
|
+
expect(res).to be_a_kind_of(Array)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should return an empty array if no work is found in the queue' do
|
83
|
+
n = 2
|
84
|
+
strategy.instance_variable_set(:@queue, sized_queue)
|
85
|
+
allow(sized_queue).to receive(:size).and_return(0)
|
86
|
+
allow(sized_queue).to receive(:pop).and_return(work)
|
87
|
+
expect(sized_queue).to receive(:pop).exactly(0).times
|
88
|
+
res = strategy.provide_work(n)
|
89
|
+
expect(res.size).to eq(0)
|
90
|
+
expect(res).to be_a_kind_of(Array)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should return units of work from the return queue first' do
|
94
|
+
n = 2
|
95
|
+
strategy.instance_variable_set(:@return_queue, return_queue)
|
96
|
+
allow(return_queue).to receive(:empty?).and_return(false)
|
97
|
+
allow(return_queue).to receive(:size).and_return(10)
|
98
|
+
allow(return_queue).to receive(:pop).and_return(work)
|
99
|
+
expect(return_queue).to receive(:pop).exactly(n).times
|
100
|
+
res = strategy.provide_work(n)
|
101
|
+
expect(res.size).to eq(n)
|
102
|
+
expect(res).to be_a_kind_of(Array)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return units of work from all queues if return queue is small' do
|
106
|
+
n = 2
|
107
|
+
|
108
|
+
strategy.instance_variable_set(:@return_queue, return_queue)
|
109
|
+
allow(return_queue).to receive(:empty?).and_return(false, true)
|
110
|
+
allow(return_queue).to receive(:size).and_return(1)
|
111
|
+
allow(return_queue).to receive(:pop).and_return(work)
|
112
|
+
expect(return_queue).to receive(:pop).once
|
113
|
+
|
114
|
+
strategy.instance_variable_set(:@queue, sized_queue)
|
115
|
+
allow(sized_queue).to receive(:size).and_return(1)
|
116
|
+
allow(sized_queue).to receive(:pop).and_return(work)
|
117
|
+
expect(sized_queue).to receive(:pop).once
|
118
|
+
|
119
|
+
res = strategy.provide_work(n)
|
120
|
+
expect(res.size).to eq(n)
|
121
|
+
expect(res).to be_a_kind_of(Array)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'return_work' do
|
126
|
+
it 'should add it to the internal return queue' do
|
127
|
+
strategy.instance_variable_set(:@return_queue, [])
|
128
|
+
strategy.send(:return_work, [work])
|
129
|
+
strategy.send(:return_work, [work])
|
130
|
+
return_queue = strategy.instance_variable_get(:@return_queue)
|
131
|
+
expect(return_queue).to eq([work, work])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context '#consume' do
|
136
|
+
it 'should create a consumer object, add it to the list of consumers and start a consumer thread' do
|
137
|
+
allow(strategy).to receive(:start_consumer_thread).and_return(true)
|
138
|
+
expect(strategy).to receive(:start_consumer_thread)
|
139
|
+
strategy.send(:consume, consume_queue)
|
140
|
+
expect(strategy.instance_variable_get(:@consumers)).to be_a_kind_of(Array)
|
141
|
+
expect(strategy.instance_variable_get(:@consumers).first).to be_a_kind_of(TestConsumer)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context '#start_consumer_thread' do
|
146
|
+
let(:thread) { double('thread') }
|
147
|
+
|
148
|
+
it 'should create a thread' do
|
149
|
+
allow(Thread).to receive(:new).and_return(thread)
|
150
|
+
res = strategy.send(:start_consumer_thread, consumer_object)
|
151
|
+
expect(res).to eq(thread)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context '#create_work_units' do
|
156
|
+
it 'should create an unit of work from what the consumer gets, and adds it to the internal queue' do
|
157
|
+
strategy.instance_variable_set(:@queue, [])
|
158
|
+
res = strategy.send(:create_work_units, consumer_object)
|
159
|
+
internal_queue = strategy.instance_variable_get(:@queue)
|
160
|
+
expect(internal_queue).to be_a_kind_of(Array)
|
161
|
+
expect(internal_queue.first).to be_a_kind_of(Chore::UnitOfWork)
|
162
|
+
expect(internal_queue.first.id).to eq(msg)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -8,11 +8,26 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
8
8
|
allow(strategy).to receive(:exit!)
|
9
9
|
strategy
|
10
10
|
end
|
11
|
+
let(:consumer) { double('consumer', :complete => nil, :reject => nil) }
|
11
12
|
let(:job_timeout) { 60 }
|
12
|
-
let(:job)
|
13
|
+
let(:job) do
|
14
|
+
Chore::UnitOfWork.new(
|
15
|
+
SecureRandom.uuid,
|
16
|
+
nil,
|
17
|
+
'test',
|
18
|
+
job_timeout,
|
19
|
+
Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
|
20
|
+
0,
|
21
|
+
consumer
|
22
|
+
)
|
23
|
+
end
|
13
24
|
let!(:worker) { Chore::Worker.new(job) }
|
14
25
|
let(:pid) { Random.rand(2048) }
|
15
26
|
|
27
|
+
before(:each) do
|
28
|
+
allow(consumer).to receive(:duplicate_message?).and_return(false)
|
29
|
+
end
|
30
|
+
|
16
31
|
after(:each) do
|
17
32
|
allow(Process).to receive(:kill) { nil }
|
18
33
|
allow(Process).to receive(:wait) { pid }
|
@@ -164,6 +179,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
164
179
|
end
|
165
180
|
|
166
181
|
it 'should run the on_failure callback hook' do
|
182
|
+
allow(Chore).to receive(:run_hooks_for)
|
167
183
|
forker.assign(job)
|
168
184
|
expect(Chore).to receive(:run_hooks_for).with(:on_failure, anything, instance_of(Chore::TimeoutError))
|
169
185
|
sleep 2
|
@@ -281,4 +297,3 @@ describe Chore::Strategy::ForkedWorkerStrategy do
|
|
281
297
|
end
|
282
298
|
end
|
283
299
|
end
|
284
|
-
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
describe Chore::Strategy::Ipc do
|
5
|
+
class DummyClass
|
6
|
+
include Chore::Strategy::Ipc
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@dummy_instance = DummyClass.new
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:socket) { double('socket') }
|
14
|
+
let(:connection) { double('socket') }
|
15
|
+
let(:message) { "test message" }
|
16
|
+
let(:encoded_message) { "#{[Marshal.dump(message).size].pack('L>')}#{Marshal.dump(message)}" }
|
17
|
+
let(:socket_base) { './prefork_worker_sock-' }
|
18
|
+
let(:pid) { '1' }
|
19
|
+
let(:socket_file) { socket_base + pid }
|
20
|
+
|
21
|
+
describe '#create_master_socket' do
|
22
|
+
before(:each) do
|
23
|
+
allow(Process).to receive(:pid).and_return(pid)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'deletes socket file, if it already exists' do
|
27
|
+
allow(File).to receive(:exist?).with(socket_file).and_return(true)
|
28
|
+
allow(UNIXServer).to receive(:new).and_return(socket)
|
29
|
+
allow(socket).to receive(:setsockopt).and_return(true)
|
30
|
+
expect(File).to receive(:delete).with(socket_file)
|
31
|
+
@dummy_instance.create_master_socket
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should create and return a new UnixServer object' do
|
35
|
+
allow(File).to receive(:exist?).with(socket_file).and_return(false)
|
36
|
+
allow(UNIXServer).to receive(:new).and_return(socket)
|
37
|
+
allow(socket).to receive(:setsockopt).and_return(true)
|
38
|
+
expect(@dummy_instance.create_master_socket).to eq(socket)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should set the required socket options' do
|
42
|
+
allow(File).to receive(:exist?).with(socket_file).and_return(false)
|
43
|
+
allow(UNIXServer).to receive(:new).and_return(socket)
|
44
|
+
expect(socket).to receive(:setsockopt).with(:SOCKET, :REUSEADDR, true)
|
45
|
+
@dummy_instance.create_master_socket
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context '#child_connection' do
|
50
|
+
it 'should accept a connection and return the connection socket' do
|
51
|
+
expect(socket).to receive(:accept).and_return(connection)
|
52
|
+
expect(@dummy_instance.child_connection(socket)).to eq connection
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context '#send_msg' do
|
57
|
+
it 'should raise an exception if the message is empty' do
|
58
|
+
expect { @dummy_instance.send_msg(socket, nil) }.to raise_error('send_msg cannot send empty messages')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should send a message with the predefined protocol (size of message + marshalled message)' do
|
62
|
+
expect(socket).to receive(:send).with(encoded_message, 0)
|
63
|
+
@dummy_instance.send_msg(socket, message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context '#read_msg' do
|
68
|
+
before(:each) do
|
69
|
+
allow(IO).to receive(:select).with([socket], nil, nil, 0.5).and_return([[socket], [], []])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should return nil if the message size is missing' do
|
73
|
+
allow(socket).to receive(:recv).and_return(nil)
|
74
|
+
expect(@dummy_instance.read_msg(socket)).to eq(nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should read a message with the predefined protocol (size of message + marshalled message)' do
|
78
|
+
allow(socket).to receive(:recv).and_return(encoded_message)
|
79
|
+
expect(@dummy_instance.read_msg(socket)).to eq(message)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should raise an exception if the connection was dropped' do
|
83
|
+
allow(socket).to receive(:recv).and_raise(Errno::ECONNRESET)
|
84
|
+
expect { @dummy_instance.read_msg(socket) }.to raise_error(Errno::ECONNRESET)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context '#add_worker_socket' do
|
89
|
+
it 'should create and return a new UnixSocket object' do
|
90
|
+
allow(UNIXSocket).to receive(:new).and_return(socket)
|
91
|
+
allow(socket).to receive(:setsockopt).and_return(true)
|
92
|
+
expect(@dummy_instance.add_worker_socket).to eq(socket)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should set the required socket options' do
|
96
|
+
allow(UNIXSocket).to receive(:new).and_return(socket)
|
97
|
+
expect(socket).to receive(:setsockopt).with(:SOCKET, :REUSEADDR, true)
|
98
|
+
@dummy_instance.add_worker_socket
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context '#clear_ready' do
|
103
|
+
it 'should remove the ready signal from the the socket' do
|
104
|
+
expect(socket).to receive(:gets)
|
105
|
+
@dummy_instance.clear_ready(socket)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context '#signal_ready' do
|
110
|
+
it 'should set a ready signal on the socket' do
|
111
|
+
expect(socket).to receive(:puts).with('R')
|
112
|
+
@dummy_instance.signal_ready(socket)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context '#select_sockets' do
|
117
|
+
it 'should return a readable socket if one is found' do
|
118
|
+
allow(IO).to receive(:select).with([socket], nil, [socket], 0.5).and_return([[socket], [], []])
|
119
|
+
expect(@dummy_instance.select_sockets([socket], nil, 0.5)).to eq([[socket], [], []])
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should timeout and return no sockets if none are found within the timeout window' do
|
123
|
+
expect(@dummy_instance.select_sockets(nil, nil, 0.1)).to eq(nil)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|