chore-core 1.8.2 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +6 -0
  3. data/chore-core.gemspec +1 -0
  4. data/lib/chore.rb +11 -5
  5. data/lib/chore/cli.rb +21 -2
  6. data/lib/chore/consumer.rb +15 -5
  7. data/lib/chore/fetcher.rb +12 -7
  8. data/lib/chore/hooks.rb +2 -1
  9. data/lib/chore/job.rb +17 -0
  10. data/lib/chore/manager.rb +18 -2
  11. data/lib/chore/queues/filesystem/consumer.rb +116 -59
  12. data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
  13. data/lib/chore/queues/filesystem/publisher.rb +12 -18
  14. data/lib/chore/queues/sqs/consumer.rb +6 -21
  15. data/lib/chore/strategies/consumer/batcher.rb +8 -9
  16. data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +3 -1
  17. data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +121 -0
  18. data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
  19. data/lib/chore/strategies/worker/helpers/ipc.rb +88 -0
  20. data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
  21. data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
  22. data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
  23. data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
  24. data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
  25. data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
  26. data/lib/chore/strategies/worker/single_worker_strategy.rb +35 -13
  27. data/lib/chore/unit_of_work.rb +8 -0
  28. data/lib/chore/util.rb +5 -1
  29. data/lib/chore/version.rb +3 -3
  30. data/lib/chore/worker.rb +29 -0
  31. data/spec/chore/cli_spec.rb +2 -2
  32. data/spec/chore/consumer_spec.rb +0 -4
  33. data/spec/chore/duplicate_detector_spec.rb +17 -5
  34. data/spec/chore/fetcher_spec.rb +0 -11
  35. data/spec/chore/manager_spec.rb +7 -0
  36. data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +71 -11
  37. data/spec/chore/queues/sqs/consumer_spec.rb +1 -3
  38. data/spec/chore/strategies/consumer/batcher_spec.rb +50 -0
  39. data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +1 -0
  40. data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
  41. data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +16 -1
  42. data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
  43. data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
  44. data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
  45. data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
  46. data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
  47. data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
  48. data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
  49. data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +25 -0
  50. data/spec/chore/worker_spec.rb +69 -1
  51. metadata +33 -5
@@ -34,9 +34,7 @@ describe Chore::Queues::SQS::Consumer do
34
34
 
35
35
  expect(AWS::SQS).to receive(:new).with(
36
36
  :access_key_id => 'key',
37
- :secret_access_key => 'secret',
38
- :logger => Chore.logger,
39
- :log_level => :debug
37
+ :secret_access_key => 'secret'
40
38
  ).and_return(sqs)
41
39
  consumer.consume
42
40
  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
@@ -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
 
@@ -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,25 @@ 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) { Chore::UnitOfWork.new(SecureRandom.uuid, 'test', job_timeout, Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])), 0) }
13
+ let(:job) do
14
+ Chore::UnitOfWork.new(
15
+ SecureRandom.uuid,
16
+ 'test',
17
+ job_timeout,
18
+ Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
19
+ 0,
20
+ consumer
21
+ )
22
+ end
13
23
  let!(:worker) { Chore::Worker.new(job) }
14
24
  let(:pid) { Random.rand(2048) }
15
25
 
26
+ before(:each) do
27
+ allow(consumer).to receive(:duplicate_message?).and_return(false)
28
+ end
29
+
16
30
  after(:each) do
17
31
  allow(Process).to receive(:kill) { nil }
18
32
  allow(Process).to receive(:wait) { pid }
@@ -164,6 +178,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
164
178
  end
165
179
 
166
180
  it 'should run the on_failure callback hook' do
181
+ allow(Chore).to receive(:run_hooks_for)
167
182
  forker.assign(job)
168
183
  expect(Chore).to receive(:run_hooks_for).with(:on_failure, anything, instance_of(Chore::TimeoutError))
169
184
  sleep 2
@@ -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
+
@@ -0,0 +1,236 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chore::Strategy::PreforkedWorker do
4
+ before(:each) do
5
+ allow_any_instance_of(Chore::Strategy::PreforkedWorker).to receive(:post_fork_setup)
6
+ end
7
+
8
+ let(:preforkedworker) { Chore::Strategy::PreforkedWorker.new }
9
+ let(:socket) { double("socket") }
10
+ let(:work) { double("work") }
11
+ let(:consumer) { double("consumer") }
12
+ let(:worker) { double("worker") }
13
+ let(:config) { double("config") }
14
+ let(:queues) { double("queues") }
15
+ let(:consumer_object) { double("consumer_object") }
16
+
17
+ context '#start_worker' do
18
+ it 'should connect to the master to signal that it is ready, and process messages with the worker' do
19
+ allow(preforkedworker).to receive(:connect_to_master).and_return(socket)
20
+ expect(preforkedworker).to receive(:connect_to_master).with(socket)
21
+ allow(preforkedworker).to receive(:worker).and_return(true)
22
+ expect(preforkedworker).to receive(:worker).with(socket)
23
+ preforkedworker.start_worker(socket)
24
+ end
25
+ end
26
+
27
+ describe '#worker' do
28
+ before(:each) do
29
+ preforkedworker.instance_variable_set(:@self_read, socket)
30
+ allow(preforkedworker).to receive(:select_sockets).and_return([[socket],nil,nil])
31
+ allow(preforkedworker).to receive(:read_msg).and_return(nil)
32
+ allow(preforkedworker).to receive(:is_orphan?).and_return(false)
33
+ allow(preforkedworker).to receive(:process_work).and_return(true)
34
+ allow(preforkedworker).to receive(:signal_ready).and_return(true)
35
+ allow(work).to receive(:queue_timeout).and_return(20*60)
36
+ end
37
+
38
+
39
+ it 'should not run while @running is false' do
40
+ allow(preforkedworker).to receive(:running?).and_return(false)
41
+ expect(preforkedworker).to receive(:select_sockets).exactly(0).times
42
+ begin
43
+ preforkedworker.send(:worker, nil)
44
+ rescue SystemExit=>e
45
+ expect(e.status).to eq(0)
46
+ end
47
+ end
48
+
49
+ it 'should be able to handle timeouts on readable sockets' do
50
+ allow(preforkedworker).to receive(:running?).and_return(true, true, false)
51
+ allow(preforkedworker).to receive(:select_sockets).and_return(nil)
52
+ expect(preforkedworker).to receive(:select_sockets).exactly(2).times
53
+ begin
54
+ preforkedworker.send(:worker, nil)
55
+ rescue SystemExit=>e
56
+ expect(e.status).to eq(0)
57
+ end
58
+ end
59
+
60
+ it 'should read a message if the connection is readable' do
61
+ allow(preforkedworker).to receive(:running?).and_return(true, false)
62
+ expect(preforkedworker).to receive(:read_msg).once
63
+ begin
64
+ preforkedworker.send(:worker, nil)
65
+ rescue SystemExit=>e
66
+ expect(e.status).to eq(0)
67
+ end
68
+ end
69
+
70
+ it 'should check if the master is alive, and if not, it should end' do
71
+ allow(preforkedworker).to receive(:running).and_return(true)
72
+ allow(preforkedworker).to receive(:select_sockets).and_return([[socket],nil,nil])
73
+ allow(preforkedworker).to receive(:read_msg).and_return(nil)
74
+ allow(preforkedworker).to receive(:is_orphan?).and_return(true)
75
+ begin
76
+ preforkedworker.send(:worker, socket)
77
+ rescue SystemExit=>e
78
+ expect(e.status).to eq(0)
79
+ end
80
+ end
81
+
82
+ it 'should process work if it is read from the master and signal ready' do
83
+ allow(preforkedworker).to receive(:running?).and_return(true, false)
84
+ allow(preforkedworker).to receive(:read_msg).and_return(work)
85
+ expect(preforkedworker).to receive(:process_work).once
86
+ expect(preforkedworker).to receive(:signal_ready).once
87
+ begin
88
+ preforkedworker.send(:worker, socket)
89
+ rescue SystemExit=>e
90
+ expect(e.status).to eq(0)
91
+ end
92
+ end
93
+
94
+ it 'should exit the process if the connection to master is closed' do
95
+ allow(preforkedworker).to receive(:running?).and_return(true)
96
+ allow(preforkedworker).to receive(:read_msg).and_raise(Errno::ECONNRESET)
97
+ begin
98
+ preforkedworker.send(:worker, socket)
99
+ rescue SystemExit=>e
100
+ expect(e.status).to eq(0)
101
+ end
102
+ end
103
+ end
104
+
105
+ context '#connect_to_master' do
106
+ it 'should create a connection to the master, and send it its PID and a ready message' do
107
+ allow(preforkedworker).to receive(:child_connection).and_return(socket)
108
+ allow(preforkedworker).to receive(:send_msg).and_return(true)
109
+ allow(preforkedworker).to receive(:signal_ready).and_return(true)
110
+
111
+ expect(preforkedworker).to receive(:child_connection).once
112
+ expect(preforkedworker).to receive(:send_msg).once
113
+ expect(preforkedworker).to receive(:signal_ready).once
114
+
115
+ res = preforkedworker.send(:connect_to_master,socket)
116
+
117
+ expect(res).to eq(socket)
118
+ end
119
+ end
120
+
121
+ context '#post_fork_setup' do
122
+ before(:each) do
123
+ allow(preforkedworker).to receive(:procline).and_return(true)
124
+ allow(preforkedworker).to receive(:trap_signals)
125
+ allow_any_instance_of(Chore::Strategy::PreforkedWorker).to receive(:post_fork_setup).and_call_original
126
+ end
127
+
128
+ it 'should change the process name' do
129
+ expect(preforkedworker).to receive(:procline)
130
+ preforkedworker.send(:post_fork_setup)
131
+ end
132
+
133
+ it 'should trap new relevant signals' do
134
+ expect(preforkedworker).to receive(:trap_signals)
135
+ preforkedworker.send(:post_fork_setup)
136
+ end
137
+
138
+ it "should run after_fork hooks" do
139
+ hook_called = false
140
+ Chore.add_hook(:after_fork) { hook_called = true }
141
+ preforkedworker.send(:post_fork_setup)
142
+ expect(hook_called).to be true
143
+ end
144
+ end
145
+
146
+ context '#process_work' do
147
+ before(:each) do
148
+ allow(preforkedworker).to receive(:consumer).and_return(consumer)
149
+ allow(work).to receive(:queue_name).and_return("test_queue")
150
+ allow(work).to receive(:consumer=)
151
+ allow(work).to receive(:queue_timeout).and_return(10)
152
+ allow(Chore::Worker).to receive(:new).and_return(worker)
153
+ allow(worker).to receive(:start)
154
+ end
155
+
156
+ it 'should fetch the consumer object associated with the queue' do
157
+ expect(preforkedworker).to receive(:consumer)
158
+ preforkedworker.send(:process_work, [work])
159
+ end
160
+
161
+ it 'should create and start a worker object with the job sent to it' do
162
+ expect(worker).to receive(:start)
163
+ preforkedworker.send(:process_work, [work])
164
+ end
165
+
166
+ it 'should timeout if the work runs for longer than the queue timeout' do
167
+ allow(work).to receive(:queue_timeout).and_return(1)
168
+ allow(worker).to receive(:start) { sleep 5 }
169
+ begin
170
+ preforkedworker.send(:process_work, [work])
171
+ rescue => e
172
+ expect(e.class).to eq(Timeout::Error)
173
+ end
174
+ end
175
+ end
176
+
177
+ context '#consumer' do
178
+ before(:each) do
179
+ preforkedworker.instance_variable_set(:@consumer_cache, {key_1: :value_1})
180
+ allow(Chore).to receive(:config).and_return(config)
181
+ allow(config).to receive(:queues).and_return(queues)
182
+ allow(queues).to receive(:size).and_return(2)
183
+ allow(config).to receive(:consumer).and_return(consumer)
184
+ allow(consumer).to receive(:new).and_return(:value_2)
185
+ end
186
+
187
+ it 'should fetch a consumer object if it was created previously for this queue' do
188
+ preforkedworker.instance_variable_set(:@consumer_cache, {key_1: :value_1})
189
+ res = preforkedworker.send(:consumer,:key_1)
190
+ expect(res).to eq(:value_1)
191
+ end
192
+
193
+ it 'should create and return a new consumer object if one does not exist for this queue' do
194
+ preforkedworker.instance_variable_set(:@consumer_cache, {})
195
+ expect(consumer).to receive(:new)
196
+ res = preforkedworker.send(:consumer,:key_2)
197
+ expect(res).to eq(:value_2)
198
+ end
199
+ end
200
+
201
+ context '#trap_signals' do
202
+ it 'should reset signals' do
203
+ allow(Chore::Signal).to receive(:reset)
204
+ allow(Chore::Signal).to receive(:trap)
205
+ expect(Chore::Signal).to receive(:reset)
206
+ preforkedworker.send(:trap_signals)
207
+ end
208
+
209
+ it 'should trap the signals passed to it' do
210
+ allow(Chore::Signal).to receive(:reset)
211
+ allow(Chore::Signal).to receive(:trap)
212
+ expect(Chore::Signal).to receive(:trap).with(:INT).once
213
+ expect(Chore::Signal).to receive(:trap).with(:QUIT).once
214
+ expect(Chore::Signal).to receive(:trap).with(:TERM).once
215
+ expect(Chore::Signal).to receive(:trap).with(:USR1).once
216
+ preforkedworker.send(:trap_signals)
217
+ end
218
+ end
219
+
220
+ context '#is_orphan?' do
221
+ it 'should return true if the parent is dead' do
222
+ allow(Process).to receive(:ppid).and_return(10)
223
+ preforkedworker.instance_variable_set(:@manager_pid, 9)
224
+ res = preforkedworker.send(:is_orphan?)
225
+ expect(res).to eq(true)
226
+ end
227
+
228
+ it 'should return false if the parent is alive' do
229
+ allow(Process).to receive(:ppid).and_return(10)
230
+ preforkedworker.instance_variable_set(:@manager_pid, 10)
231
+ res = preforkedworker.send(:is_orphan?)
232
+ expect(res).to eq(false)
233
+ end
234
+ end
235
+ end
236
+