chore-core 1.10.0 → 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 +5 -13
- data/LICENSE.txt +1 -1
- data/README.md +172 -153
- data/chore-core.gemspec +3 -3
- data/lib/chore.rb +29 -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 +17 -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 +10 -16
- 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 +6 -6
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +7 -6
- 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/unit_of_work.rb +2 -1
- data/lib/chore/util.rb +5 -1
- data/lib/chore/version.rb +2 -2
- data/lib/chore/worker.rb +30 -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/single_consumer_strategy_spec.rb +3 -3
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +6 -6
- data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +6 -1
- data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
- data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
- data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
- data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
- data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
- data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
- data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +1 -1
- data/spec/chore/worker_spec.rb +70 -15
- data/spec/spec_helper.rb +1 -1
- data/spec/support/queues/sqs/fake_objects.rb +18 -0
- metadata +53 -29
@@ -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
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'chore/strategies/worker/helpers/work_distributor'
|
3
|
+
|
4
|
+
describe Chore::Strategy::WorkDistributor do
|
5
|
+
let(:timestamp) { Time.now }
|
6
|
+
let(:manager) { double('manager') }
|
7
|
+
let(:worker) { Chore::Strategy::WorkerInfo.new(1) }
|
8
|
+
let(:consumer) { double('consumer') }
|
9
|
+
let(:job) do
|
10
|
+
Chore::UnitOfWork.new(
|
11
|
+
SecureRandom.uuid,
|
12
|
+
'test',
|
13
|
+
60,
|
14
|
+
Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
|
15
|
+
0
|
16
|
+
)
|
17
|
+
end
|
18
|
+
let(:socket) { double('socket') }
|
19
|
+
|
20
|
+
context '#include_ipc' do
|
21
|
+
it 'should include the Ipc module' do
|
22
|
+
expect(described_class.ipc_help).to eq(:available)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#fetch_and_assign_jobs' do
|
27
|
+
it 'should fetch jobs from the consumer' do
|
28
|
+
allow(described_class).to receive(:assign_jobs).and_return(true)
|
29
|
+
allow(manager).to receive(:fetch_work).with(1).and_return([job])
|
30
|
+
allow(manager).to receive(:return_work)
|
31
|
+
expect(manager).to receive(:fetch_work).with(1)
|
32
|
+
described_class.fetch_and_assign_jobs([worker], manager)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should assign the fetched jobs to the workers' do
|
36
|
+
allow(manager).to receive(:fetch_work).with(1).and_return([job])
|
37
|
+
allow(manager).to receive(:return_work)
|
38
|
+
expect(described_class).to receive(:assign_jobs).with([job], [worker])
|
39
|
+
described_class.fetch_and_assign_jobs([worker], manager)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should not return any work' do
|
43
|
+
allow(described_class).to receive(:assign_jobs).and_return([])
|
44
|
+
allow(manager).to receive(:fetch_work).with(1).and_return([job])
|
45
|
+
expect(manager).to receive(:return_work).with([])
|
46
|
+
described_class.fetch_and_assign_jobs([worker], manager)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should raise and exception if it does not get an array from the manager' do
|
50
|
+
allow(manager).to receive(:fetch_work).with(1).and_return(nil)
|
51
|
+
allow(manager).to receive(:return_work)
|
52
|
+
expect { described_class.fetch_and_assign_jobs([worker], manager) }.to raise_error("DW: jobs needs to be a list got NilClass")
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should sleep if no jobs are available' do
|
56
|
+
expect(described_class).to receive(:sleep).with(0.1)
|
57
|
+
allow(manager).to receive(:fetch_work).with(1).and_return([])
|
58
|
+
allow(manager).to receive(:return_work)
|
59
|
+
described_class.fetch_and_assign_jobs([worker], manager)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#assign_jobs' do
|
64
|
+
it 'should raise an exception if we have no free workers' do
|
65
|
+
expect { described_class.send(:assign_jobs, [job], []) }.to raise_error('DW: assign_jobs got 0 workers')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should remove the consumer object from the job object' do
|
69
|
+
allow(described_class).to receive(:push_job_to_worker).and_return(true)
|
70
|
+
|
71
|
+
described_class.send(:assign_jobs, [job], [worker])
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should raise an exception if more jobs than workers are provided to it' do
|
75
|
+
allow(described_class).to receive(:push_job_to_worker).and_return(true)
|
76
|
+
|
77
|
+
expect { described_class.send(:assign_jobs, [job, job], [worker]) }.to raise_error('DW: More Jobs than Sockets')
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should send the job object to the free worker' do
|
81
|
+
allow(described_class).to receive(:push_job_to_worker).and_return(true)
|
82
|
+
|
83
|
+
expect(described_class).to receive(:push_job_to_worker).with(job, worker)
|
84
|
+
described_class.send(:assign_jobs, [job], [worker])
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return jobs that failed to be assigned' do
|
88
|
+
job2 = Chore::UnitOfWork.new(
|
89
|
+
SecureRandom.uuid,
|
90
|
+
'test',
|
91
|
+
60,
|
92
|
+
Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])),
|
93
|
+
0
|
94
|
+
)
|
95
|
+
worker2 = Chore::Strategy::WorkerInfo.new(2)
|
96
|
+
|
97
|
+
allow(described_class).to receive(:push_job_to_worker).and_return(true, false)
|
98
|
+
|
99
|
+
expect(described_class).to receive(:push_job_to_worker).with(job, worker)
|
100
|
+
expect(described_class).to receive(:push_job_to_worker).with(job2, worker2)
|
101
|
+
unassigned_jobs = described_class.send(:assign_jobs, [job, job2], [worker, worker2])
|
102
|
+
expect(unassigned_jobs).to eq([job2])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context '#push_job_to_worker' do
|
107
|
+
before(:each) do
|
108
|
+
allow(described_class).to receive(:clear_ready).with(worker.socket).and_return(true)
|
109
|
+
allow(described_class).to receive(:send_msg).with(worker.socket, job).and_return(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should clear all signals from the worker' do
|
113
|
+
expect(described_class).to receive(:clear_ready).with(worker.socket)
|
114
|
+
described_class.send(:push_job_to_worker, job, worker)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should send the job as a message on the worker' do
|
118
|
+
expect(described_class).to receive(:send_msg).with(worker.socket, job)
|
119
|
+
described_class.send(:push_job_to_worker, job, worker)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should return true if job successfully sent' do
|
123
|
+
expect(described_class.send(:push_job_to_worker, job, worker)).to eq(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should return false if job failed to send' do
|
127
|
+
expect(described_class).to receive(:send_msg).and_raise(StandardError)
|
128
|
+
expect(described_class.send(:push_job_to_worker, job, worker)).to eq(false)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|