worker_roulette 0.1.1 → 0.1.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.
- 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:
|