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 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: false, host: 'localhost', timeout: 5, db: 1)
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); work_orders! {|work_orders| on_message_callback.call(work_orders, message, channel)} if 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 sender_on_job_board = redis.call('ZSCORE', job_board_key, sender_key)
51
+ local sender_is_on_job_board = redis.call('ZSCORE', job_board_key, sender_key)
52
52
 
53
- if (sender_on_job_board == false) then
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 = sender_key .. ' added'
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 :sender
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 {self.unsubscribe; block.call(work_orders!) if block}
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, nil], [@namespace]) do |results|
22
- @sender = (results.first || '').split(':').first
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 empty = KEYS[2]
46
- local namespace = ARGV[1]
42
+ local last_sender_key = KEYS[2]
43
+ local sender_key = ARGV[1]
47
44
 
48
- local function drain_work_orders(job_board_key, namespace)
49
- local sender_key = redis.call('ZRANGE', job_board_key, 0, 0)[1]
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 == false then
52
- return {}
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 results = {}
56
- results[1] = sender_key
57
- results[2] = redis.call('LRANGE', sender_key, 0, -1)
58
- results[3] = redis.call('DEL', sender_key)
59
- results[4] = redis.call('ZREM', job_board_key, sender_key)
60
- return results
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, namespace)
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
@@ -1,3 +1,3 @@
1
1
  module WorkerRoulette
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -1,4 +1,4 @@
1
- require_relative '../spec_helper'
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
- WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
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.bmbm do |x|
53
- x.report "Time to evently insert and read #{ITERATIONS} large work_orders" do # ~4200 work_orders / second round trip; 50-50 read-write time; CPU and IO bound
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.bmbm do |x|
75
- x.report "Time to evently pubsub insert and read #{ITERATIONS} large work_orders" do # ~5200 work_orders / second round trip; 50-50 read-write time; CPU and IO bound
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.sender.should == sender
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
- subject.sender.should == "katie_80"
185
- redis_work_orders.should == [work_orders_with_headers]
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 be working on behalf of a sender" do
96
- subject.work_orders!
97
- subject.sender.should == sender
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
@@ -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.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-19 00:00:00.000000000 Z
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: