nexia_worker_roulette 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ require 'worker_roulette'
2
+ require 'benchmark'
3
+ require 'eventmachine'
4
+
5
+ REDIS_CONNECTION_POOL_SIZE = 100
6
+ ITERATIONS = 10_000
7
+
8
+ work_order = {'ding dong' => "hello_foreman_" * 100}
9
+
10
+ EM::Hiredis.reconnect_timeout = 0.01
11
+
12
+ puts "Redis Connection Pool Size: #{REDIS_CONNECTION_POOL_SIZE}"
13
+
14
+ times = Benchmark.bm do |x|
15
+ x.report "#{ITERATIONS} ASync Api Read/Writes" do
16
+ EM.run do
17
+ WorkerRoulette.start(evented: true)
18
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
19
+ @total = 0
20
+ @tradesman = WorkerRoulette.tradesman
21
+
22
+ ITERATIONS.times do |iteration|
23
+ sender = 'sender_' + iteration.to_s
24
+ foreman = WorkerRoulette.foreman(sender)
25
+ foreman.enqueue_work_order(work_order) do
26
+ @tradesman.work_orders! do
27
+ @total += 1
28
+ EM.stop if @total == (ITERATIONS - 1)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ puts "#{(ITERATIONS / times.first.real).to_i} ASync Api Read/Writes per second"
36
+ puts "#################"
37
+ puts
38
+
39
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
40
+
41
+ times = Benchmark.bm do |x|
42
+ x.report "#{ITERATIONS * 2} ASync Api Polling Read/Writes" do
43
+ EM.run do
44
+ @processed = 0
45
+ @total = 0
46
+ WorkerRoulette.start(evented: true)
47
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
48
+ @total = 0
49
+ @tradesman = WorkerRoulette.tradesman
50
+ ITERATIONS.times do |iteration|
51
+ @start ||= Time.now
52
+ sender = 'sender_' + iteration.to_s
53
+ foreman = WorkerRoulette.foreman(sender)
54
+ foreman.enqueue_work_order(work_order)
55
+ end
56
+ @tradesman.wait_for_work_orders {@processed += 1; ((@stop = Time.now) && EM.add_timer(1){EM.stop}) if @processed == (ITERATIONS - 1)}
57
+ end
58
+ end
59
+ end
60
+ puts "#{ITERATIONS * 2 / (@stop - @start)} ASync Api Polling Read/Writes per second"
61
+ puts "#################"
62
+ puts
63
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
64
+
65
+ WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
66
+ times = Benchmark.bm do |x|
67
+ puts x.class.name
68
+ x.report "#{ITERATIONS} Sync Api Writes" do
69
+ ITERATIONS.times do |iteration|
70
+ sender = 'sender_' + iteration.to_s
71
+ foreman = WorkerRoulette.foreman(sender)
72
+ foreman.enqueue_work_order(work_order)
73
+ end
74
+ end
75
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
76
+ end
77
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
78
+
79
+ puts "#{(ITERATIONS / times.first.real).to_i} Sync Api Writes per second"
80
+ puts "#################"
81
+ puts
82
+ ITERATIONS.times do |iteration|
83
+ sender = 'sender_' + iteration.to_s
84
+ foreman = WorkerRoulette.foreman(sender)
85
+ foreman.enqueue_work_order(work_order)
86
+ end
87
+
88
+ times = Benchmark.bm do |x|
89
+ x.report "#{ITERATIONS} Sync Api Reads" do
90
+ ITERATIONS.times do |iteration|
91
+ sender = 'sender_' + iteration.to_s
92
+ tradesman = WorkerRoulette.tradesman
93
+ tradesman.work_orders!
94
+ end
95
+ end
96
+ end
97
+ puts "#{(ITERATIONS / times.first.real).to_i} Sync Api Reads per second"
98
+ puts "#################"
99
+ puts
100
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
101
+
102
+ times = Benchmark.bm do |x|
103
+ x.report "#{ITERATIONS} Sync Api Read/Writes" do
104
+ ITERATIONS.times do |iteration|
105
+ sender = 'sender_' + iteration.to_s
106
+ foreman = WorkerRoulette.foreman(sender)
107
+ foreman.enqueue_work_order(work_order)
108
+ end
109
+
110
+ ITERATIONS.times do |iteration|
111
+ sender = 'sender_' + iteration.to_s
112
+ tradesman = WorkerRoulette.tradesman
113
+ tradesman.work_orders!
114
+ end
115
+ end
116
+ end
117
+ puts "#{(ITERATIONS / times.first.real).to_i} Sync Api Read/Writes per second"
118
+ puts "#################"
119
+ puts
120
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
121
+
122
+ times = Benchmark.bm do |x|
123
+ x.report "#{ITERATIONS * 2} Sync Api Polling Read/Writes" do
124
+ WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
125
+ ITERATIONS.times do |iteration|
126
+ sender = 'sender_' + iteration.to_s
127
+ foreman = WorkerRoulette.foreman(sender)
128
+ foreman.enqueue_work_order(work_order)
129
+ tradesman = WorkerRoulette.tradesman
130
+ tradesman.wait_for_work_orders {|m| m}
131
+ end
132
+ end
133
+ end
134
+ puts "#{(ITERATIONS * 2 / times.first.real).to_i} Sync Api Polling Read/Writes per second"
135
+ puts "#################"
136
+ puts
137
+ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
@@ -0,0 +1 @@
1
+ .gitkeep
@@ -0,0 +1,250 @@
1
+ require "spec_helper"
2
+ module WorkerRoulette
3
+ describe WorkerRoulette do
4
+ include EventedSpec::EMSpec
5
+
6
+ let(:sender) {'katie_80'}
7
+ let(:work_orders) {["hello", "foreman"]}
8
+ let(:default_headers) {Hash['headers' => {'sender' => sender}]}
9
+ let(:hello_work_order) {Hash['payload' => "hello"]}
10
+ let(:foreman_work_order) {Hash['payload' => "foreman"]}
11
+ let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
12
+ let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
13
+ let(:worker_roulette) { WorkerRoulette.start(evented: true) }
14
+ let(:redis) {Redis.new(worker_roulette.redis_config)}
15
+
16
+ context "Evented Foreman" do
17
+ let(:subject) {worker_roulette.foreman(sender)}
18
+
19
+ it "enqueues work" do
20
+ called = false
21
+ foreman = worker_roulette.foreman('foreman')
22
+ foreman.enqueue_work_order('some old fashion work') do |redis_response, stuff|
23
+ called = true
24
+ end
25
+ done(0.1) { expect(called).to be_truthy }
26
+ end
27
+
28
+ it "should enqueue_work_order two work_orders in the sender's slot in the job board" do
29
+ subject.enqueue_work_order(work_orders.first) do
30
+ subject.enqueue_work_order(work_orders.last) do
31
+ expected = work_orders.map { |m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
32
+ expect(redis.lrange(sender, 0, -1)).to eq(expected)
33
+ done
34
+ end
35
+ end
36
+ end
37
+
38
+ it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the job board" do
39
+ subject.enqueue_work_order_without_headers(work_orders) do
40
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
41
+ done
42
+ end
43
+ end
44
+
45
+ it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the job board" do
46
+ subject.enqueue_work_order(work_orders) do
47
+ expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
48
+ done
49
+ end
50
+ end
51
+
52
+ it "should enqueue_work_order an array of work_orders with additional headers in the sender's slot in the job board" do
53
+ extra_headers = {'foo' => 'bars'}
54
+ subject.enqueue_work_order(work_orders, extra_headers) do
55
+ work_orders_with_headers['headers'].merge!(extra_headers)
56
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
57
+ done
58
+ end
59
+ end
60
+
61
+ it "should post the sender's id to the job board with an order number" do
62
+ first_foreman = worker_roulette.foreman('first_foreman')
63
+ first_foreman.enqueue_work_order('foo') do
64
+ subject.enqueue_work_order(work_orders.first) do
65
+ subject.enqueue_work_order(work_orders.last) do
66
+ expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([["first_foreman", 1.0], ["katie_80", 2.0]])
67
+ done
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
74
+ first_foreman = worker_roulette.foreman('first_foreman')
75
+ expect(redis.get(subject.counter_key)).to be_nil
76
+ first_foreman.enqueue_work_order(work_orders.first) do
77
+ expect(redis.get(subject.counter_key)).to eq("1")
78
+ first_foreman.enqueue_work_order(work_orders.last) do
79
+ expect(redis.get(subject.counter_key)).to eq("1")
80
+ subject.enqueue_work_order(work_orders.first) do
81
+ expect(redis.get(subject.counter_key)).to eq("2")
82
+ done
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ context "Evented Tradesman" do
90
+ let(:foreman) {worker_roulette.foreman(sender)}
91
+ let(:subject) {worker_roulette.tradesman(nil, 0.01) }
92
+
93
+ it "should be working on behalf of a sender" do
94
+ foreman.enqueue_work_order(work_orders) do
95
+ subject.work_orders! do |r|
96
+ expect(subject.last_sender).to eq(sender)
97
+ done
98
+ end
99
+ end
100
+ end
101
+
102
+ it "should remove the lock from the last_sender's queue" do
103
+ most_recent_sender = 'most_recent_sender'
104
+ most_recent_foreman = worker_roulette.foreman(most_recent_sender)
105
+ other_foreman = worker_roulette.foreman('katie_80')
106
+
107
+ other_foreman.enqueue_work_order(work_orders) do
108
+ most_recent_foreman.enqueue_work_order(work_orders) do
109
+ expect(redis.keys("L*:*").length).to eq(0)
110
+ subject.work_orders! do
111
+ expect(redis.get("L*:katie_80")).to eq("1")
112
+ expect(redis.keys("L*:*").length).to eq(1)
113
+ subject.work_orders! do
114
+ expect(redis.keys("L*:*").length).to eq(1)
115
+ expect(redis.get("L*:most_recent_sender")).to eq("1")
116
+ subject.work_orders!
117
+ done(0.2) do
118
+ expect(redis.keys("L*:*").length).to eq(0)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ it "should drain one set of work_orders from the sender's slot in the job board" do
127
+ foreman.enqueue_work_order(work_orders) do
128
+ subject.work_orders! do |r|
129
+ expect(r).to eq([work_orders_with_headers])
130
+ subject.work_orders! do |r| expect(r).to be_empty
131
+ subject.work_orders! {|r| expect(r).to be_empty; done} #does not throw an error if queue is alreay empty
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ it "should take the oldest sender off the job board (FIFO)" do
138
+ foreman.enqueue_work_order(work_orders) do
139
+ oldest_sender = sender.to_s
140
+ most_recent_sender = 'most_recent_sender'
141
+ most_recent_foreman = worker_roulette.foreman(most_recent_sender)
142
+ most_recent_foreman.enqueue_work_order(work_orders) do
143
+ expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
144
+ subject.work_orders! { expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([most_recent_sender]); done }
145
+ end
146
+ end
147
+ end
148
+
149
+ it "should get the work_orders from the next queue when a new job is ready" do
150
+ #tradesman polls every so often, we care that it is called at least twice, but did not use
151
+ #the built in rspec syntax for that bc if the test ends while we're talking to redis, redis
152
+ #throws an Error. This way we ensure we call work_orders! at least twice and just stub the second
153
+ #call so as not to hurt redis' feelings.
154
+
155
+ expect(subject).to receive(:work_orders!).and_call_original
156
+ expect(subject).to receive(:work_orders!)
157
+
158
+ foreman.enqueue_work_order(work_orders) do
159
+ subject.wait_for_work_orders do |redis_work_orders|
160
+ expect(redis_work_orders).to eq([work_orders_with_headers])
161
+ expect(subject.last_sender).to match(/katie_80/)
162
+ done(0.1)
163
+ end
164
+ end
165
+ end
166
+
167
+ it "should publish and subscribe on custom channels" do
168
+ good_subscribed = false
169
+ bad_subscribed = false
170
+
171
+ tradesman = worker_roulette.tradesman('good_channel', 0.001)
172
+ evil_tradesman = worker_roulette.tradesman('bad_channel', 0.001)
173
+
174
+ good_foreman = worker_roulette.foreman('foreman', 'good_channel')
175
+ bad_foreman = worker_roulette.foreman('foreman', 'bad_channel')
176
+
177
+ #tradesman polls every so often, we care that it is called at least twice, but did not use
178
+ #the built in rspec syntax for that bc if the test ends while we're talking to redis, redis
179
+ #throws an Error. This way we ensure we call work_orders! at least twice and just stub the second
180
+ #call so as not to hurt redis' feelings.
181
+ expect(tradesman).to receive(:work_orders!).and_call_original
182
+ expect(tradesman).to receive(:work_orders!)
183
+
184
+ expect(evil_tradesman).to receive(:work_orders!).and_call_original
185
+ expect(evil_tradesman).to receive(:work_orders!)
186
+
187
+ good_foreman.enqueue_work_order('some old fashion work') do
188
+ bad_foreman.enqueue_work_order('evil biddings you should not carry out') do
189
+
190
+ tradesman.wait_for_work_orders do |good_work|
191
+ expect(good_work.to_s).to match("old fashion")
192
+ expect(good_work.to_s).not_to match("evil")
193
+ end
194
+
195
+ evil_tradesman.wait_for_work_orders do |bad_work|
196
+ expect(bad_work.to_s).not_to match("old fashion")
197
+ expect(bad_work.to_s).to match("evil")
198
+ end
199
+ done(0.1)
200
+
201
+ end
202
+ end
203
+ end
204
+
205
+ it "should pull off work orders for more than one sender" do
206
+ tradesman = worker_roulette.tradesman('good_channel')
207
+
208
+ good_foreman = worker_roulette.foreman('good_foreman', 'good_channel')
209
+ lazy_foreman = worker_roulette.foreman('lazy_foreman', 'good_channel')
210
+
211
+ got_good = false
212
+ got_lazy = false
213
+ good_foreman.enqueue_work_order('do good work') do
214
+ tradesman.work_orders! do |r|
215
+ got_good = true
216
+ expect(r.first['payload']).to eq('do good work')
217
+ end
218
+ end
219
+ lazy_foreman.enqueue_work_order('just get it done') do
220
+ tradesman.work_orders! do |r|
221
+ got_lazy = true
222
+ expect(r.first['payload']).to eq('just get it done')
223
+ end
224
+ end
225
+
226
+ done(0.2) {expect(got_good && got_lazy).to eq(true)}
227
+ end
228
+ end
229
+
230
+ pending "should return a hash with a string in the payload if OJ cannot parse the json"
231
+
232
+ context "Failure" do
233
+ it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; done; end
234
+ end
235
+
236
+ context "Concurrent Access" do
237
+ it "should not leak connections"
238
+
239
+ it "should be fork() proof" do
240
+ @subject = worker_roulette.tradesman
241
+ @subject.work_orders! do
242
+ fork do
243
+ @subject.work_orders!
244
+ end
245
+ end
246
+ done(1)
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,172 @@
1
+ require "spec_helper"
2
+ module WorkerRoulette
3
+ describe WorkerRoulette do
4
+ let(:sender) {'katie_80'}
5
+ let(:work_orders) {["hello", "foreman"]}
6
+ let(:default_headers) {Hash['headers' => {'sender' => sender}]}
7
+ let(:hello_work_order) {Hash['payload' => "hello"]}
8
+ let(:foreman_work_order) {Hash['payload' => "foreman"]}
9
+ let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
10
+ let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
11
+ let(:worker_roulette) { WorkerRoulette.start }
12
+
13
+ let(:redis) {Redis.new(worker_roulette.redis_config)}
14
+
15
+ before do
16
+ redis.flushall
17
+ end
18
+
19
+ it "should exist" do
20
+ expect(worker_roulette).to be_instance_of(WorkerRoulette)
21
+ end
22
+
23
+ context Foreman do
24
+ let(:subject) {worker_roulette.foreman(sender)}
25
+
26
+ it "should be working on behalf of a sender" do
27
+ expect(subject.sender).to eq(sender)
28
+ end
29
+
30
+ it "should enqueue_work_order two work_orders in the sender's work queue" do
31
+ subject.enqueue_work_order(work_orders.first) {}
32
+ subject.enqueue_work_order(work_orders.last) {}
33
+ expect(redis.lrange(sender, 0, -1)).to eq(work_orders.map {|m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) })
34
+ end
35
+
36
+ it "should enqueue_work_order an array of work_orders without headers in the sender's work queue" do
37
+ subject.enqueue_work_order_without_headers(work_orders)
38
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
39
+ end
40
+
41
+ it "should enqueue_work_order an array of work_orders with default headers in the sender's work queue" do
42
+ subject.enqueue_work_order(work_orders)
43
+ expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
44
+ end
45
+
46
+ it "should enqueue_work_order an array of work_orders with additional headers in the sender's work queue" do
47
+ extra_headers = {'foo' => 'bars'}
48
+ subject.enqueue_work_order(work_orders, extra_headers)
49
+ work_orders_with_headers['headers'].merge!(extra_headers)
50
+ expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
51
+ end
52
+
53
+ it "should post the sender's id to the job board with an order number" do
54
+ subject.enqueue_work_order(work_orders.first)
55
+ worker_roulette.foreman('other_forman').enqueue_work_order(work_orders.last)
56
+ expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([[sender, 1.0], ["other_forman", 2.0]])
57
+ end
58
+
59
+ it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
60
+ other_forman = worker_roulette.foreman('other_forman')
61
+ expect(redis.get(subject.counter_key)).to be_nil
62
+ subject.enqueue_work_order(work_orders.first)
63
+ expect(redis.get(subject.counter_key)).to eq("1")
64
+ subject.enqueue_work_order(work_orders.last)
65
+ expect(redis.get(subject.counter_key)).to eq("1")
66
+ other_forman.enqueue_work_order(work_orders.last)
67
+ expect(redis.get(other_forman.counter_key)).to eq("2")
68
+ end
69
+ end
70
+
71
+ context Tradesman do
72
+ let(:foreman) { worker_roulette.foreman(sender) }
73
+ let(:tradesman) { worker_roulette.tradesman }
74
+
75
+ before do
76
+ foreman.enqueue_work_order(work_orders)
77
+ end
78
+
79
+ context 'removing locks from queues' do
80
+ it "for the last_sender's queue" do
81
+ most_recent_sender = 'most_recent_sender'
82
+ most_recent_foreman = worker_roulette.foreman(most_recent_sender)
83
+ most_recent_foreman.enqueue_work_order(work_orders)
84
+ expect(redis.keys("L*:*").length).to eq(0)
85
+ tradesman.work_orders!
86
+ expect(redis.get("L*:katie_80")).to eq("1")
87
+ expect(redis.keys("L*:*").length).to eq(1)
88
+ tradesman.work_orders!
89
+ expect(redis.keys("L*:*").length).to eq(1)
90
+ expect(redis.get("L*:most_recent_sender")).to eq("1")
91
+ tradesman.work_orders!
92
+ expect(redis.keys("L*:*").length).to eq(0)
93
+ end
94
+ end
95
+
96
+ it "should have a last sender if it found messages" do
97
+ expect(tradesman.work_orders!.length).to eq(1)
98
+ expect(tradesman.last_sender).to eq(sender)
99
+ end
100
+
101
+ it "should not have a last sender if it found no messages" do
102
+ expect(tradesman.work_orders!.length).to eq(1)
103
+ expect(tradesman.work_orders!.length).to eq(0)
104
+ expect(tradesman.last_sender).to be_nil
105
+ end
106
+
107
+ it "should drain one set of work_orders from the sender's work queue" do
108
+ expect(tradesman.work_orders!).to eq([work_orders_with_headers])
109
+ expect(tradesman.work_orders!).to be_empty
110
+ expect(tradesman.work_orders!).to be_empty #does not throw an error if queue is already empty
111
+ end
112
+
113
+ it "should drain all the work_orders from the sender's work queue" do
114
+ foreman.enqueue_work_order(work_orders)
115
+ expect(tradesman.work_orders!).to eq([work_orders_with_headers, work_orders_with_headers])
116
+ expect(tradesman.work_orders!).to be_empty
117
+ expect(tradesman.work_orders!).to be_empty #does not throw an error if queue is already empty
118
+ end
119
+
120
+ it "should take the oldest sender off the job board (FIFO)" do
121
+ oldest_sender = sender.to_s
122
+ most_recent_sender = 'most_recent_sender'
123
+ most_recent_foreman = worker_roulette.foreman(most_recent_sender)
124
+ most_recent_foreman.enqueue_work_order(work_orders)
125
+ expect(redis.zrange(tradesman.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
126
+ tradesman.work_orders!
127
+ expect(redis.zrange(tradesman.job_board_key, 0, -1)).to eq([most_recent_sender])
128
+ end
129
+
130
+ it "should get the work_orders from the next queue when a new job is ready, then poll for new work" do
131
+ tradesman.wait_for_work_orders do |redis_work_orders|
132
+ expect(redis_work_orders).to eq([work_orders_with_headers])
133
+ expect(tradesman.last_sender).to eq('katie_80')
134
+ allow(tradesman).to receive(:wait_for_work_orders)
135
+ end
136
+ end
137
+
138
+ it "should publish and subscribe on custom channels" do
139
+ tradesman = worker_roulette.tradesman('good_channel')
140
+ expect(tradesman).to receive(:work_orders!).and_call_original
141
+
142
+ good_foreman = worker_roulette.foreman('foreman', 'good_channel')
143
+ bad_foreman = worker_roulette.foreman('foreman', 'bad_channel')
144
+
145
+ good_foreman.enqueue_work_order('some old fashion work')
146
+ bad_foreman.enqueue_work_order('evil biddings you should not carry out')
147
+
148
+ tradesman.wait_for_work_orders do |work|
149
+ expect(work.to_s).to match("some old fashion work")
150
+ expect(work.to_s).not_to match("evil")
151
+ expect(tradesman.last_sender).to eq('foreman')
152
+ allow(tradesman).to receive(:wait_for_work_orders)
153
+ end
154
+ end
155
+
156
+ context "Failure" do
157
+ it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; end
158
+ end
159
+
160
+ context "Concurrent Access" do
161
+ it "should pool its connections" do
162
+ Array.new(100) do
163
+ Thread.new {worker_roulette.tradesman_connection_pool.with {|pooled_redis| pooled_redis.get("foo")}}
164
+ end.each(&:join)
165
+ worker_roulette.tradesman_connection_pool.with do |pooled_redis|
166
+ expect(pooled_redis.info["connected_clients"].to_i).to be > (worker_roulette.pool_size)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,18 @@
1
+ module WorkerRoulette
2
+ require 'worker_roulette'
3
+ require 'evented-spec'
4
+ require 'rspec'
5
+ require 'pry'
6
+
7
+ require File.expand_path(File.join("..", "..", "lib", "worker_roulette.rb"), __FILE__)
8
+
9
+ Dir[File.join(File.dirname(__FILE__), 'helpers', '**/*.rb')].sort.each { |file| require file.gsub(".rb", "")}
10
+
11
+ EM::Hiredis.reconnect_timeout = 0.01
12
+
13
+ RSpec.configure do |c|
14
+ c.after(:each) do
15
+ Redis.new(WorkerRoulette.start.redis_config).flushdb
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+ module WorkerRoulette
3
+ describe "Evented Read Lock" do
4
+ include EventedSpec::EMSpec
5
+
6
+ let(:redis) {Redis.new(WorkerRoulette.start.redis_config)}
7
+ let(:sender) {'katie_80'}
8
+ let(:work_orders) {"hellot"}
9
+ let(:lock_key) {"L*:#{sender}"}
10
+ let(:default_headers) {Hash['headers' => {'sender' => sender}]}
11
+ let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
12
+ let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
13
+ let(:worker_roulette) { WorkerRoulette.start(evented: true) }
14
+ let(:foreman) {worker_roulette.foreman(sender)}
15
+ let(:number_two) {worker_roulette.foreman('number_two')}
16
+ let(:subject) {worker_roulette.tradesman}
17
+ let(:subject_two) {worker_roulette.tradesman}
18
+ let(:lua) { Lua.new(worker_roulette.tradesman_connection_pool) }
19
+
20
+ em_before do
21
+ lua.clear_cache!
22
+ redis.script(:flush)
23
+ redis.flushdb
24
+ end
25
+
26
+ it "should lock a queue when it reads from it" do
27
+ evented_readlock_preconditions do
28
+ expect(redis.get(lock_key)).not_to be_nil
29
+ done
30
+ end
31
+ end
32
+
33
+ it "should set the lock to expire in 3 seconds" do
34
+ evented_readlock_preconditions do
35
+ expect(redis.ttl(lock_key)).to eq(3)
36
+ done
37
+ end
38
+ end
39
+
40
+ it "should not read a locked queue" do
41
+ evented_readlock_preconditions do
42
+ foreman.enqueue_work_order(work_orders) do #locked
43
+ subject_two.work_orders! { |work| expect(work).to be_empty; done}
44
+ end
45
+ end
46
+ end
47
+
48
+ it "should read from the first available queue that is not locked" do
49
+ evented_readlock_preconditions do
50
+ foreman.enqueue_work_order(work_orders) do #locked
51
+ number_two.enqueue_work_order(work_orders) do #unlocked
52
+ subject_two.work_orders!{|work| expect(work.first['headers']['sender']).to eq('number_two'); done}
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ it "should release its last lock when it asks for its next work order from another sender" do
59
+ evented_readlock_preconditions do
60
+ number_two.enqueue_work_order(work_orders) do #unlocked
61
+ expect(subject.last_sender).to eq(sender)
62
+ subject.work_orders! do |work|
63
+ expect(work.first['headers']['sender']).to eq('number_two')
64
+ expect(redis.get(lock_key)).to be_nil
65
+ done
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ it "should not release its lock when it asks for its next work order from the same sender" do
72
+ evented_readlock_preconditions do
73
+ foreman.enqueue_work_order(work_orders) do #locked
74
+ subject.work_orders! do |work|
75
+ expect(work).to eq([work_orders_with_headers])
76
+ expect(subject.last_sender).to eq(sender)
77
+ expect(redis.get(lock_key)).not_to be_nil
78
+ done
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ it "should not take out another lock if there is no work to do" do
85
+ evented_readlock_preconditions do
86
+ foreman.enqueue_work_order(work_orders) do #locked
87
+ subject.work_orders! do |work_order|
88
+ expect(work_order).to eq([work_orders_with_headers])
89
+ subject.work_orders! do |work|
90
+ expect(work).to be_empty
91
+ expect(redis.get(lock_key)).to be_nil
92
+ done
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def evented_readlock_preconditions(&spec_block)
100
+ foreman.enqueue_work_order(work_orders) do
101
+ subject.work_orders! do |work|
102
+ expect(work).to eq([work_orders_with_headers])
103
+ spec_block.call
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end