worker_roulette 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/lib/worker_roulette/a_tradesman.rb +10 -1
- data/lib/worker_roulette/foreman.rb +3 -3
- data/lib/worker_roulette/tradesman.rb +39 -25
- data/lib/worker_roulette/version.rb +1 -1
- data/spec/benchmark/perf_test.rb +89 -44
- data/spec/integration/evented_worker_roulette_spec.rb +6 -46
- data/spec/integration/worker_roulette_spec.rb +13 -5
- data/spec/unit/evented_readlock_spec.rb +107 -0
- data/spec/unit/lua_spec.rb +49 -0
- data/spec/unit/readlock_spec.rb +74 -0
- data/worker_roulette.gemspec +2 -0
- metadata +40 -2
data/README.md
CHANGED
@@ -10,7 +10,7 @@ size_of_connection_pool = 100
|
|
10
10
|
|
11
11
|
#Start it up
|
12
12
|
#the config takes size for the connection pool size, evented to specify which api to use, then the normal redis config
|
13
|
-
WorkerRoulette.start(size: size_of_connection_pool, evented:
|
13
|
+
WorkerRoulette.start(size: size_of_connection_pool, evented: true, host: 'localhost', timeout: 5, db: 1)
|
14
14
|
|
15
15
|
#Enqueue some work
|
16
16
|
sender_id = :shady
|
@@ -4,7 +4,7 @@ module WorkerRoulette
|
|
4
4
|
def wait_for_work_orders(on_subscribe_callback = nil, &on_message_callback)
|
5
5
|
@redis_pubsub ||= WorkerRoulette.new_redis_pubsub #cannot use connection pool bc redis expects each obj to own its own pubsub connection for the life of the subscription
|
6
6
|
@redis_pubsub.on(:subscribe) {|channel, subscription_count| on_subscribe_callback.call(channel, subscription_count) if on_subscribe_callback}
|
7
|
-
@redis_pubsub.on(:message) {|channel, message| set_timer(on_message_callback);
|
7
|
+
@redis_pubsub.on(:message) {|channel, message| set_timer(on_message_callback); get_messages(message, channel, on_message_callback)}
|
8
8
|
@redis_pubsub.subscribe(@channel)
|
9
9
|
end
|
10
10
|
|
@@ -24,6 +24,15 @@ module WorkerRoulette
|
|
24
24
|
|
25
25
|
private
|
26
26
|
attr_reader :timer
|
27
|
+
def get_messages(message, channel, on_message_callback)
|
28
|
+
return unless on_message_callback
|
29
|
+
work_orders! do |work_orders_1|
|
30
|
+
work_orders! do |work_orders|
|
31
|
+
on_message_callback.call(work_orders_1 + work_orders, message, channel)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
27
36
|
def set_timer(on_message_callback)
|
28
37
|
return unless on_message_callback
|
29
38
|
@timer && @timer.cancel
|
@@ -48,12 +48,12 @@ module WorkerRoulette
|
|
48
48
|
|
49
49
|
local function enqueue_work_orders(work_order, job_notification)
|
50
50
|
local result = sender_key .. ' updated'
|
51
|
-
local
|
51
|
+
local sender_is_on_job_board = redis.call('ZSCORE', job_board_key, sender_key)
|
52
52
|
|
53
|
-
if (
|
53
|
+
if (sender_is_on_job_board == false) then
|
54
54
|
local count = redis.call('INCR', counter_key)
|
55
55
|
local job_added = redis.call('ZADD',job_board_key, count, sender_key)
|
56
|
-
result
|
56
|
+
result = sender_key .. ' added'
|
57
57
|
end
|
58
58
|
|
59
59
|
local work_added = redis.call('RPUSH',sender_key, work_order)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module WorkerRoulette
|
2
2
|
class Tradesman
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :last_sender
|
4
4
|
def initialize(client_pool, pubsub_pool, namespace = nil)
|
5
5
|
@client_pool = client_pool
|
6
6
|
@pubsub_pool = pubsub_pool
|
@@ -12,14 +12,15 @@ module WorkerRoulette
|
|
12
12
|
@pubsub_pool.with do |redis|
|
13
13
|
redis.subscribe(@channel) do |on|
|
14
14
|
on.subscribe {on_subscribe_callback.call if on_subscribe_callback}
|
15
|
-
on.message {
|
15
|
+
on.message {block.call(work_orders! + work_orders!) if block}
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
def work_orders!(&callback)
|
21
|
-
Lua.call(self.class.lua_drain_work_orders, [job_board_key,
|
22
|
-
|
21
|
+
Lua.call(self.class.lua_drain_work_orders, [job_board_key, @last_sender], [nil]) do |results|
|
22
|
+
results ||= []
|
23
|
+
@last_sender = (results.first || '').split(':').first
|
23
24
|
work = (results[1] || []).map {|work_order| WorkerRoulette.load(work_order)}
|
24
25
|
callback.call work if callback
|
25
26
|
work
|
@@ -35,37 +36,50 @@ module WorkerRoulette
|
|
35
36
|
end
|
36
37
|
|
37
38
|
private
|
38
|
-
def sender_key
|
39
|
-
@sender_key = WorkerRoulette.sender_key(@sender, @namespace)
|
40
|
-
end
|
41
|
-
|
42
39
|
def self.lua_drain_work_orders
|
43
40
|
<<-HERE
|
44
41
|
local job_board_key = KEYS[1]
|
45
|
-
local
|
46
|
-
local
|
42
|
+
local last_sender_key = KEYS[2]
|
43
|
+
local sender_key = ARGV[1]
|
47
44
|
|
48
|
-
local function drain_work_orders(job_board_key,
|
49
|
-
|
45
|
+
local function drain_work_orders(job_board_key, last_sender_key, sender_key)
|
46
|
+
if last_sender_key ~= "" and last_sender_key ~= nil then
|
47
|
+
local last_sender_lock_key = 'L*:' .. last_sender_key
|
48
|
+
redis.call('DEL', last_sender_lock_key)
|
49
|
+
end
|
50
50
|
|
51
|
-
if sender_key ==
|
52
|
-
|
51
|
+
if (not sender_key) or (sender_key == "") then
|
52
|
+
sender_key = redis.call('ZRANGE', job_board_key, 0, 0)[1]
|
53
|
+
if (not sender_key) or (sender_key == "") then
|
54
|
+
return {}
|
55
|
+
end
|
53
56
|
end
|
54
57
|
|
55
|
-
local
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
local lock_key = 'L*:' .. sender_key
|
59
|
+
local locked = (redis.call('GET', lock_key) == 'L')
|
60
|
+
|
61
|
+
if not locked then
|
62
|
+
local results = {}
|
63
|
+
results[1] = sender_key
|
64
|
+
results[2] = redis.call('LRANGE', sender_key, 0, -1)
|
65
|
+
redis.call('DEL', sender_key)
|
66
|
+
redis.call('ZREM', job_board_key, sender_key)
|
67
|
+
redis.call('SET', lock_key, 'L', 'EX', 1, 'NX')
|
68
|
+
return results
|
69
|
+
else
|
70
|
+
local sender_index = redis.call('ZRANK', job_board_key, sender_key)
|
71
|
+
local next_index = sender_index + 1
|
72
|
+
local next_sender_key = redis.call('ZRANGE', job_board_key, next_index, next_index)[1]
|
73
|
+
if next_sender_key then
|
74
|
+
return drain_work_orders(job_board_key, "", next_sender_key)
|
75
|
+
else
|
76
|
+
return {}
|
77
|
+
end
|
78
|
+
end
|
61
79
|
end
|
62
80
|
|
63
|
-
return drain_work_orders(job_board_key,
|
81
|
+
return drain_work_orders(job_board_key, last_sender_key, "")
|
64
82
|
HERE
|
65
83
|
end
|
66
|
-
|
67
|
-
def get_sender_for_next_job(redis)
|
68
|
-
@sender = (redis.zrange(job_board_key, 0, 0) || []).first.to_s
|
69
|
-
end
|
70
84
|
end
|
71
85
|
end
|
data/spec/benchmark/perf_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'worker_roulette'
|
2
2
|
require 'benchmark'
|
3
3
|
require 'eventmachine'
|
4
4
|
|
@@ -6,51 +6,13 @@ REDIS_CONNECTION_POOL_SIZE = 100
|
|
6
6
|
ITERATIONS = 10_000
|
7
7
|
|
8
8
|
work_order = {'ding dong' => "hello_foreman_" * 100}
|
9
|
-
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
10
|
-
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
11
|
-
|
12
|
-
puts "Redis Connection Pool Size: #{REDIS_CONNECTION_POOL_SIZE}"
|
13
|
-
|
14
|
-
Benchmark.bmbm do |x|
|
15
|
-
x.report "Time to insert and read #{ITERATIONS} large work_orders" do # ~2500 work_orders / second round trip; 50-50 read-write time; CPU and IO bound
|
16
|
-
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
17
|
-
ITERATIONS.times do |iteration|
|
18
|
-
sender = 'sender_' + iteration.to_s
|
19
|
-
foreman = WorkerRoulette.foreman(sender)
|
20
|
-
foreman.enqueue_work_order(work_order)
|
21
|
-
end
|
22
|
-
|
23
|
-
ITERATIONS.times do |iteration|
|
24
|
-
sender = 'sender_' + iteration.to_s
|
25
|
-
tradesman = WorkerRoulette.tradesman
|
26
|
-
tradesman.work_orders!
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
9
|
|
31
10
|
EM::Hiredis.reconnect_timeout = 0.01
|
32
11
|
|
33
|
-
|
34
|
-
|
35
|
-
Benchmark.bmbm do |x|
|
36
|
-
x.report "Time for tradesmans to enqueue_work_order and read #{ITERATIONS} large work_orders via pubsub" do # ~2700 work_orders / second round trip
|
37
|
-
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
38
|
-
ITERATIONS.times do |iteration|
|
39
|
-
p = -> do
|
40
|
-
sender = 'sender_' + iteration.to_s
|
41
|
-
foreman = WorkerRoulette.foreman(sender)
|
42
|
-
foreman.enqueue_work_order(work_order)
|
43
|
-
end
|
44
|
-
tradesman = WorkerRoulette.tradesman
|
45
|
-
tradesman.wait_for_work_orders(p) {|m| m; tradesman.unsubscribe}
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
12
|
+
puts "Redis Connection Pool Size: #{REDIS_CONNECTION_POOL_SIZE}"
|
51
13
|
|
52
|
-
Benchmark.
|
53
|
-
x.report "
|
14
|
+
times = Benchmark.bm do |x|
|
15
|
+
x.report "#{ITERATIONS} ASync Api Read/Writes" do
|
54
16
|
EM.run do
|
55
17
|
WorkerRoulette.start(evented: true)
|
56
18
|
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
@@ -70,9 +32,14 @@ Benchmark.bmbm do |x|
|
|
70
32
|
end
|
71
33
|
end
|
72
34
|
end
|
35
|
+
puts "#{ITERATIONS / times.first.real} ASync Api Read/Writes per second"
|
36
|
+
puts "#################"
|
37
|
+
puts
|
38
|
+
|
39
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
73
40
|
|
74
|
-
Benchmark.
|
75
|
-
x.report "
|
41
|
+
times = Benchmark.bm do |x|
|
42
|
+
x.report "#{ITERATIONS * 2} ASync Api Pubsub Read/Writes" do
|
76
43
|
EM.run do
|
77
44
|
WorkerRoulette.start(evented: true)
|
78
45
|
@processed = 0
|
@@ -92,5 +59,83 @@ Benchmark.bmbm do |x|
|
|
92
59
|
end
|
93
60
|
end
|
94
61
|
end
|
62
|
+
puts "#{ITERATIONS * 2 / times.first.real} ASync Api Pubsub Read/Writes per second"
|
63
|
+
puts "#################"
|
64
|
+
puts
|
65
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
66
|
+
|
67
|
+
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
68
|
+
times = Benchmark.bm do |x|
|
69
|
+
puts x.class.name
|
70
|
+
x.report "#{ITERATIONS} Sync Api Writes" do
|
71
|
+
ITERATIONS.times do |iteration|
|
72
|
+
sender = 'sender_' + iteration.to_s
|
73
|
+
foreman = WorkerRoulette.foreman(sender)
|
74
|
+
foreman.enqueue_work_order(work_order)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
78
|
+
end
|
79
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
95
80
|
|
81
|
+
puts "#{ITERATIONS / times.first.real} Sync Api Writes per second"
|
82
|
+
puts "#################"
|
83
|
+
puts
|
84
|
+
ITERATIONS.times do |iteration|
|
85
|
+
sender = 'sender_' + iteration.to_s
|
86
|
+
foreman = WorkerRoulette.foreman(sender)
|
87
|
+
foreman.enqueue_work_order(work_order)
|
88
|
+
end
|
89
|
+
|
90
|
+
times = Benchmark.bm do |x|
|
91
|
+
x.report "#{ITERATIONS} Sync Api Reads" do
|
92
|
+
ITERATIONS.times do |iteration|
|
93
|
+
sender = 'sender_' + iteration.to_s
|
94
|
+
tradesman = WorkerRoulette.tradesman
|
95
|
+
tradesman.work_orders!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
puts "#{ITERATIONS / times.first.real} Sync Api Reads per second"
|
100
|
+
puts "#################"
|
101
|
+
puts
|
96
102
|
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
103
|
+
|
104
|
+
times = Benchmark.bm do |x|
|
105
|
+
x.report "#{ITERATIONS} Sync Api Read/Writes" do
|
106
|
+
ITERATIONS.times do |iteration|
|
107
|
+
sender = 'sender_' + iteration.to_s
|
108
|
+
foreman = WorkerRoulette.foreman(sender)
|
109
|
+
foreman.enqueue_work_order(work_order)
|
110
|
+
end
|
111
|
+
|
112
|
+
ITERATIONS.times do |iteration|
|
113
|
+
sender = 'sender_' + iteration.to_s
|
114
|
+
tradesman = WorkerRoulette.tradesman
|
115
|
+
tradesman.work_orders!
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
puts "#{ITERATIONS / times.first.real} Sync Api Read/Writes per second"
|
120
|
+
puts "#################"
|
121
|
+
puts
|
122
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
123
|
+
|
124
|
+
times = Benchmark.bm do |x|
|
125
|
+
x.report "#{ITERATIONS * 2} Sync Api Pubsub Read/Writes" do
|
126
|
+
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
127
|
+
ITERATIONS.times do |iteration|
|
128
|
+
p = -> do
|
129
|
+
sender = 'sender_' + iteration.to_s
|
130
|
+
foreman = WorkerRoulette.foreman(sender)
|
131
|
+
foreman.enqueue_work_order(work_order)
|
132
|
+
end
|
133
|
+
tradesman = WorkerRoulette.tradesman
|
134
|
+
tradesman.wait_for_work_orders(p) {|m| m; tradesman.unsubscribe}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
puts "#{ITERATIONS * 2 / times.first.real} Sync Api Pubsub Read/Writes per second"
|
139
|
+
puts "#################"
|
140
|
+
puts
|
141
|
+
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
@@ -100,45 +100,6 @@ describe WorkerRoulette do
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
context Lua do
|
104
|
-
before do
|
105
|
-
Lua.clear_cache!
|
106
|
-
redis.script(:flush)
|
107
|
-
end
|
108
|
-
|
109
|
-
it "should load and call a lua script" do
|
110
|
-
lua_script = 'return redis.call("SET", KEYS[1], ARGV[1])'
|
111
|
-
Lua.call(lua_script, ['foo'], ['daddy']) do |result|
|
112
|
-
Lua.cache.keys.first.should == lua_script
|
113
|
-
Lua.cache.values.first.should == Digest::SHA1.hexdigest(lua_script)
|
114
|
-
result.should == "OK"
|
115
|
-
done
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
it "should send a sha instead of a script once the script has been cached" do
|
120
|
-
lua_script = 'return KEYS'
|
121
|
-
Lua.should_receive(:eval).and_call_original
|
122
|
-
|
123
|
-
Lua.call(lua_script) do |result|
|
124
|
-
|
125
|
-
Lua.should_not_receive(:eval)
|
126
|
-
Lua.call(lua_script) do |result|
|
127
|
-
result.should == []
|
128
|
-
done
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should raise an error to the caller if the script fails in redis" do
|
134
|
-
lua_script = 'this is junk'
|
135
|
-
# Lua.call(lua_script)
|
136
|
-
# rspec cannot test this bc of the callbacks, but if you have doubts,
|
137
|
-
# uncomment the line above and watch it fail
|
138
|
-
done
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
103
|
context "Evented Tradesman" do
|
143
104
|
let(:foreman) {WorkerRoulette.a_foreman(sender)}
|
144
105
|
let(:subject) {WorkerRoulette.a_tradesman}
|
@@ -146,7 +107,7 @@ describe WorkerRoulette do
|
|
146
107
|
it "should be working on behalf of a sender" do
|
147
108
|
foreman.enqueue_work_order(work_orders) do
|
148
109
|
subject.work_orders! do |r|
|
149
|
-
subject.
|
110
|
+
subject.last_sender.should == sender
|
150
111
|
done
|
151
112
|
end
|
152
113
|
end
|
@@ -177,15 +138,14 @@ describe WorkerRoulette do
|
|
177
138
|
end
|
178
139
|
|
179
140
|
it "should get the work_orders from the next queue when a new job is ready" do
|
180
|
-
subject.should_receive(:work_orders!).and_call_original
|
141
|
+
subject.should_receive(:work_orders!).twice.and_call_original
|
181
142
|
publish = proc {foreman.enqueue_work_order(work_orders)}
|
182
143
|
|
183
144
|
subject.wait_for_work_orders(publish) do |redis_work_orders, message, channel|
|
184
|
-
|
185
|
-
|
145
|
+
redis_work_orders.should == [work_orders_with_headers]
|
146
|
+
subject.last_sender.should == nil
|
186
147
|
done
|
187
148
|
end
|
188
|
-
|
189
149
|
end
|
190
150
|
|
191
151
|
it "should publish and subscribe on custom channels" do
|
@@ -201,8 +161,8 @@ describe WorkerRoulette do
|
|
201
161
|
good_publish = proc {good_foreman.enqueue_work_order('some old fashion work')}
|
202
162
|
bad_publish = proc {bad_foreman.enqueue_work_order('evil biddings you should not carry out')}
|
203
163
|
|
204
|
-
tradesman.should_receive(:work_orders!).and_call_original
|
205
|
-
evil_tradesman.should_receive(:work_orders!).and_call_original
|
164
|
+
tradesman.should_receive(:work_orders!).twice.and_call_original
|
165
|
+
evil_tradesman.should_receive(:work_orders!).twice.and_call_original
|
206
166
|
|
207
167
|
#They are double subscribing; is it possible that it is the connection pool?
|
208
168
|
|
@@ -92,9 +92,15 @@ describe WorkerRoulette do
|
|
92
92
|
foreman.enqueue_work_order(work_orders)
|
93
93
|
end
|
94
94
|
|
95
|
-
it "should
|
96
|
-
subject.work_orders
|
97
|
-
subject.
|
95
|
+
it "should have a last sender if it found messages" do
|
96
|
+
subject.work_orders!.length.should == 1
|
97
|
+
subject.last_sender.should == sender
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not have a last sender if it found no messages" do
|
101
|
+
subject.work_orders!.length.should == 1
|
102
|
+
subject.work_orders!.length.should == 0
|
103
|
+
subject.last_sender.should == nil
|
98
104
|
end
|
99
105
|
|
100
106
|
it "should drain one set of work_orders from the sender's slot in the switchboard" do
|
@@ -122,18 +128,19 @@ describe WorkerRoulette do
|
|
122
128
|
|
123
129
|
it "should get the work_orders from the next queue when a new job is ready" do
|
124
130
|
subject.work_orders!
|
125
|
-
subject.should_receive(:work_orders!).and_call_original
|
131
|
+
subject.should_receive(:work_orders!).twice.and_call_original
|
126
132
|
|
127
133
|
publisher = -> {foreman.enqueue_work_order(work_orders); subject.unsubscribe}
|
128
134
|
|
129
135
|
subject.wait_for_work_orders(publisher) do |redis_work_orders|
|
130
136
|
redis_work_orders.should == [work_orders_with_headers]
|
137
|
+
subject.last_sender.should == nil
|
131
138
|
end
|
132
139
|
end
|
133
140
|
|
134
141
|
it "should publish and subscribe on custom channels" do
|
135
142
|
tradesman = WorkerRoulette.tradesman('good_channel')
|
136
|
-
tradesman.should_receive(:work_orders!).and_call_original
|
143
|
+
tradesman.should_receive(:work_orders!).twice.and_call_original
|
137
144
|
|
138
145
|
good_foreman = WorkerRoulette.foreman('foreman', 'good_channel')
|
139
146
|
bad_foreman = WorkerRoulette.foreman('foreman', 'bad_channel')
|
@@ -148,6 +155,7 @@ describe WorkerRoulette do
|
|
148
155
|
tradesman.wait_for_work_orders(publish) do |work|
|
149
156
|
work.to_s.should match("some old fashion work")
|
150
157
|
work.to_s.should_not match("evil")
|
158
|
+
tradesman.last_sender.should == nil
|
151
159
|
end
|
152
160
|
end
|
153
161
|
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module WorkerRoulette
|
3
|
+
describe "Evented Read Lock" do
|
4
|
+
include EventedSpec::EMSpec
|
5
|
+
|
6
|
+
let(:redis) {Redis.new(WorkerRoulette.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(:foreman) {WorkerRoulette.foreman(sender)}
|
14
|
+
let(:number_two) {WorkerRoulette.foreman('number_two')}
|
15
|
+
let(:subject) {WorkerRoulette.tradesman}
|
16
|
+
let(:subject_two) {WorkerRoulette.tradesman}
|
17
|
+
|
18
|
+
em_before do
|
19
|
+
WorkerRoulette.start(evented: true)
|
20
|
+
Lua.clear_cache!
|
21
|
+
redis.script(:flush)
|
22
|
+
redis.flushdb
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should lock a queue when it reads from it" do
|
26
|
+
evented_readlock_preconditions do
|
27
|
+
redis.get(lock_key).should_not be_nil
|
28
|
+
done
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should set the lock to expire in 1 second" do
|
33
|
+
evented_readlock_preconditions do
|
34
|
+
redis.ttl(lock_key).should == 1
|
35
|
+
done
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not read a locked queue" do
|
40
|
+
evented_readlock_preconditions do
|
41
|
+
foreman.enqueue_work_order(work_orders) do #locked
|
42
|
+
subject_two.work_orders! {|work |work.should == []; done}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should read from the first available queue that is not locked" do
|
48
|
+
evented_readlock_preconditions do
|
49
|
+
foreman.enqueue_work_order(work_orders) do #locked
|
50
|
+
number_two.enqueue_work_order(work_orders) do #unlocked
|
51
|
+
subject_two.work_orders!{|work| work.first['headers']['sender'].should == 'number_two'; done}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should release its last lock when it asks for its next work order from another sender" do
|
58
|
+
evented_readlock_preconditions do
|
59
|
+
number_two.enqueue_work_order(work_orders) do #unlocked
|
60
|
+
subject.last_sender.should == sender
|
61
|
+
subject.work_orders! do |work|
|
62
|
+
work.first['headers']['sender'].should == 'number_two'
|
63
|
+
redis.get(lock_key).should == nil
|
64
|
+
done
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not release its lock when it asks for its next work order from the same sender" do
|
71
|
+
evented_readlock_preconditions do
|
72
|
+
foreman.enqueue_work_order(work_orders) do #locked
|
73
|
+
subject.work_orders! do |work|
|
74
|
+
work.should == [work_orders_with_headers]
|
75
|
+
subject.last_sender.should == sender
|
76
|
+
redis.get(lock_key).should_not == nil
|
77
|
+
done
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not take out another lock if there is no work to do" do
|
84
|
+
evented_readlock_preconditions do
|
85
|
+
foreman.enqueue_work_order(work_orders) do #locked
|
86
|
+
subject.work_orders! do |work|
|
87
|
+
work.should == [work_orders_with_headers]
|
88
|
+
subject.work_orders! do |work|
|
89
|
+
work.should == []
|
90
|
+
redis.get(lock_key).should == nil
|
91
|
+
done
|
92
|
+
end
|
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
|
+
work.should == [work_orders_with_headers]
|
103
|
+
spec_block.call
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module WorkerRoulette
|
3
|
+
describe Lua do
|
4
|
+
include EventedSpec::EMSpec
|
5
|
+
let(:redis) {Redis.new(WorkerRoulette.redis_config)}
|
6
|
+
|
7
|
+
em_before do
|
8
|
+
WorkerRoulette.start(evented: true)
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
Lua.clear_cache!
|
13
|
+
redis.script(:flush)
|
14
|
+
redis.flushdb
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should load and call a lua script" do
|
18
|
+
lua_script = 'return redis.call("SET", KEYS[1], ARGV[1])'
|
19
|
+
Lua.call(lua_script, ['foo'], ['daddy']) do |result|
|
20
|
+
Lua.cache.keys.first.should == lua_script
|
21
|
+
Lua.cache.values.first.should == Digest::SHA1.hexdigest(lua_script)
|
22
|
+
result.should == "OK"
|
23
|
+
done
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should send a sha instead of a script once the script has been cached" do
|
28
|
+
lua_script = 'return KEYS'
|
29
|
+
Lua.should_receive(:eval).and_call_original
|
30
|
+
|
31
|
+
Lua.call(lua_script) do |result|
|
32
|
+
|
33
|
+
Lua.should_not_receive(:eval)
|
34
|
+
Lua.call(lua_script) do |result|
|
35
|
+
result.should == []
|
36
|
+
done
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should raise an error to the caller if the script fails in redis" do
|
42
|
+
lua_script = 'this is junk'
|
43
|
+
# Lua.call(lua_script)
|
44
|
+
# rspec cannot test this bc of the callbacks, but if you have doubts,
|
45
|
+
# uncomment the line above and watch it fail
|
46
|
+
done
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module WorkerRoulette
|
3
|
+
describe "Read Lock" do
|
4
|
+
let(:redis) {Redis.new(WorkerRoulette.redis_config)}
|
5
|
+
let(:sender) {'katie_80'}
|
6
|
+
let(:work_orders) {"hellot"}
|
7
|
+
let(:lock_key) {"L*:#{sender}"}
|
8
|
+
let(:default_headers) {Hash['headers' => {'sender' => sender}]}
|
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(:foreman) {WorkerRoulette.foreman(sender)}
|
12
|
+
let(:number_two) {WorkerRoulette.foreman('number_two')}
|
13
|
+
let(:subject) {WorkerRoulette.tradesman}
|
14
|
+
let(:subject_two) {WorkerRoulette.tradesman}
|
15
|
+
|
16
|
+
before do
|
17
|
+
WorkerRoulette.start(evented: false)
|
18
|
+
Lua.clear_cache!
|
19
|
+
redis.script(:flush)
|
20
|
+
redis.flushdb
|
21
|
+
foreman.enqueue_work_order(work_orders)
|
22
|
+
subject.work_orders!.should == [work_orders_with_headers]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should lock a queue when it reads from it" do
|
26
|
+
redis.get(lock_key).should_not be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set the lock to expire in 1 second" do
|
30
|
+
redis.ttl(lock_key).should == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not read a locked queue" do
|
34
|
+
foreman.enqueue_work_order(work_orders) #locked
|
35
|
+
subject_two.work_orders!.should == []
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should read from the first available queue that is not locked" do
|
39
|
+
foreman.enqueue_work_order(work_orders) #locked
|
40
|
+
number_two.enqueue_work_order(work_orders) #unlocked
|
41
|
+
subject_two.work_orders!.first['headers']['sender'].should == 'number_two'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should release its previous lock when it asks for work from another sender" do
|
45
|
+
number_two.enqueue_work_order(work_orders) #unlocked
|
46
|
+
subject.last_sender.should == sender
|
47
|
+
subject.work_orders!.first['headers']['sender'].should == 'number_two'
|
48
|
+
redis.get(lock_key).should == nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not release its lock when it asks for work from the same sender" do
|
52
|
+
foreman.enqueue_work_order(work_orders) #locked
|
53
|
+
subject.work_orders!.should == [work_orders_with_headers]
|
54
|
+
subject.last_sender.should == sender
|
55
|
+
|
56
|
+
foreman.enqueue_work_order(work_orders) #locked
|
57
|
+
subject.work_orders!.should == [work_orders_with_headers]
|
58
|
+
subject.last_sender.should == sender
|
59
|
+
|
60
|
+
redis.get(lock_key).should_not == nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should release its previous lock if there is no work to do from the same sender" do
|
64
|
+
foreman.enqueue_work_order(work_orders) #locked
|
65
|
+
subject.work_orders!.should == [work_orders_with_headers]
|
66
|
+
subject.work_orders!.should == []
|
67
|
+
redis.get(lock_key).should == nil
|
68
|
+
end
|
69
|
+
|
70
|
+
xit "pubsub should clean up one contention orremove the lock on the same sender queue automaticly" do
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/worker_roulette.gemspec
CHANGED
@@ -32,4 +32,6 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'simplecov-rcov'
|
33
33
|
spec.add_development_dependency 'rspec_junit_formatter'
|
34
34
|
spec.add_development_dependency 'evented-spec'
|
35
|
+
spec.add_development_dependency 'guard'
|
36
|
+
spec.add_development_dependency 'guard-rspec'
|
35
37
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: worker_roulette
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-02-
|
12
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: oj
|
@@ -235,6 +235,38 @@ dependencies:
|
|
235
235
|
- - ! '>='
|
236
236
|
- !ruby/object:Gem::Version
|
237
237
|
version: '0'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: guard
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
none: false
|
242
|
+
requirements:
|
243
|
+
- - ! '>='
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
246
|
+
type: :development
|
247
|
+
prerelease: false
|
248
|
+
version_requirements: !ruby/object:Gem::Requirement
|
249
|
+
none: false
|
250
|
+
requirements:
|
251
|
+
- - ! '>='
|
252
|
+
- !ruby/object:Gem::Version
|
253
|
+
version: '0'
|
254
|
+
- !ruby/object:Gem::Dependency
|
255
|
+
name: guard-rspec
|
256
|
+
requirement: !ruby/object:Gem::Requirement
|
257
|
+
none: false
|
258
|
+
requirements:
|
259
|
+
- - ! '>='
|
260
|
+
- !ruby/object:Gem::Version
|
261
|
+
version: '0'
|
262
|
+
type: :development
|
263
|
+
prerelease: false
|
264
|
+
version_requirements: !ruby/object:Gem::Requirement
|
265
|
+
none: false
|
266
|
+
requirements:
|
267
|
+
- - ! '>='
|
268
|
+
- !ruby/object:Gem::Version
|
269
|
+
version: '0'
|
238
270
|
description: Write a gem description
|
239
271
|
email:
|
240
272
|
- classicist@gmail.com
|
@@ -260,6 +292,9 @@ files:
|
|
260
292
|
- spec/integration/evented_worker_roulette_spec.rb
|
261
293
|
- spec/integration/worker_roulette_spec.rb
|
262
294
|
- spec/spec_helper.rb
|
295
|
+
- spec/unit/evented_readlock_spec.rb
|
296
|
+
- spec/unit/lua_spec.rb
|
297
|
+
- spec/unit/readlock_spec.rb
|
263
298
|
- worker_roulette.gemspec
|
264
299
|
homepage: https://github.com/nexiahome/worker_roulette
|
265
300
|
licenses: []
|
@@ -291,4 +326,7 @@ test_files:
|
|
291
326
|
- spec/integration/evented_worker_roulette_spec.rb
|
292
327
|
- spec/integration/worker_roulette_spec.rb
|
293
328
|
- spec/spec_helper.rb
|
329
|
+
- spec/unit/evented_readlock_spec.rb
|
330
|
+
- spec/unit/lua_spec.rb
|
331
|
+
- spec/unit/readlock_spec.rb
|
294
332
|
has_rdoc:
|