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 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: