worker_roulette 0.1.0 → 0.1.1
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 +4 -4
- data/lib/worker_roulette.rb +24 -2
- data/lib/worker_roulette/a_tradesman.rb +0 -32
- data/lib/worker_roulette/foreman.rb +42 -24
- data/lib/worker_roulette/lua.rb +19 -4
- data/lib/worker_roulette/tradesman.rb +39 -17
- data/lib/worker_roulette/version.rb +1 -1
- data/spec/benchmark/perf_test.rb +2 -2
- data/spec/integration/evented_worker_roulette_spec.rb +7 -13
- data/spec/integration/worker_roulette_spec.rb +13 -33
- metadata +1 -2
- data/lib/worker_roulette/a_foreman.rb +0 -40
data/README.md
CHANGED
@@ -121,12 +121,12 @@ end
|
|
121
121
|
## Performance
|
122
122
|
Running the performance tests on my laptop, the numbers break down like this:
|
123
123
|
### Async Api
|
124
|
-
-
|
125
|
-
-
|
124
|
+
- Pubsub: ~5500 read-write round-trips / second
|
125
|
+
- Manual: ~4500 read-write round-trips / second
|
126
126
|
|
127
127
|
### Sync Api
|
128
|
-
-
|
129
|
-
-
|
128
|
+
- Pubsub: ~2700 read-write round-trips / second
|
129
|
+
- Manual: ~2500 read-write round-trips / second
|
130
130
|
|
131
131
|
To run the perf tests yourself run `bundle exec spec:perf`
|
132
132
|
|
data/lib/worker_roulette.rb
CHANGED
@@ -33,8 +33,7 @@ module WorkerRoulette
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def self.a_foreman(sender, channel = nil)
|
36
|
-
|
37
|
-
AForeman.new(sender, @foreman_connection_pool, channel)
|
36
|
+
foreman(sender, channel)
|
38
37
|
end
|
39
38
|
|
40
39
|
def self.a_tradesman(channel = nil)
|
@@ -58,6 +57,29 @@ module WorkerRoulette
|
|
58
57
|
@redis_config.dup
|
59
58
|
end
|
60
59
|
|
60
|
+
def self.dump(obj)
|
61
|
+
Oj.dump(obj)
|
62
|
+
rescue Oj::ParseError => e
|
63
|
+
{'error' => e, 'unparsable_string' => obj}
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.load(json)
|
67
|
+
Oj.load(json)
|
68
|
+
rescue Oj::ParseError => e
|
69
|
+
{'error' => e, 'unparsable_string' => obj}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.job_board_key(namespace = nil)
|
73
|
+
"#{namespace + ':' if namespace}#{WorkerRoulette::JOB_BOARD}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.sender_key(sender, namespace = nil)
|
77
|
+
"#{namespace + ':' if namespace}#{sender}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.counter_key(sender, namespace = nil)
|
81
|
+
"#{namespace + ':' if namespace}counter_key"
|
82
|
+
end
|
61
83
|
private
|
62
84
|
def self.new_redis
|
63
85
|
if @evented
|
@@ -8,13 +8,6 @@ module WorkerRoulette
|
|
8
8
|
@redis_pubsub.subscribe(@channel)
|
9
9
|
end
|
10
10
|
|
11
|
-
def work_orders!(&callback)
|
12
|
-
Lua.call(self.class.lua_drain_work_orders, [job_board_key, nil], [@namespace]) do |results|
|
13
|
-
@sender = (results.first || '').split(':').first
|
14
|
-
callback.call (results[1] || []).map {|work_order| Oj.load(work_order)} if callback
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
11
|
def unsubscribe(&callback)
|
19
12
|
deferable = @redis_pubsub.unsubscribe(@channel)
|
20
13
|
deferable.callback do
|
@@ -38,30 +31,5 @@ module WorkerRoulette
|
|
38
31
|
work_orders! {|work_orders| on_message_callback.call(work_orders, nil, nil)}
|
39
32
|
end
|
40
33
|
end
|
41
|
-
|
42
|
-
def self.lua_drain_work_orders
|
43
|
-
<<-HERE
|
44
|
-
local job_board_key = KEYS[1]
|
45
|
-
local empty = KEYS[2]
|
46
|
-
local namespace = ARGV[1]
|
47
|
-
|
48
|
-
local function drain_work_orders(job_board_key, namespace)
|
49
|
-
local sender_key = redis.call('ZRANGE', job_board_key, 0, 0)[1]
|
50
|
-
|
51
|
-
if sender_key == false then
|
52
|
-
return {}
|
53
|
-
end
|
54
|
-
|
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
|
61
|
-
end
|
62
|
-
|
63
|
-
return drain_work_orders(job_board_key, namespace)
|
64
|
-
HERE
|
65
|
-
end
|
66
34
|
end
|
67
35
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module WorkerRoulette
|
2
2
|
class Foreman
|
3
3
|
attr_reader :sender
|
4
|
-
COUNTER_KEY = 'counter_key'
|
5
4
|
|
6
5
|
def initialize(sender, redis_pool, namespace = nil)
|
7
6
|
@sender = sender
|
@@ -10,41 +9,60 @@ module WorkerRoulette
|
|
10
9
|
@channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
|
11
10
|
end
|
12
11
|
|
13
|
-
def
|
14
|
-
|
12
|
+
def enqueue_work_order(work_order, headers = {}, &callback)
|
13
|
+
work_order = {'headers' => default_headers.merge(headers), 'payload' => work_order}
|
14
|
+
enqueue_work_order_without_headers(work_order, &callback)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
17
|
+
def enqueue_work_order_without_headers(work_order, &callback)
|
18
|
+
Lua.call(self.class.lua_enqueue_work_orders, [counter_key, job_board_key, sender_key, @channel],
|
19
|
+
[WorkerRoulette.dump(work_order), WorkerRoulette::JOB_NOTIFICATIONS], &callback)
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
@
|
22
|
+
def job_board_key
|
23
|
+
@job_board_key ||= WorkerRoulette.job_board_key(@namespace)
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
-
|
27
|
-
#the count may be incremented again by another process before the sender
|
28
|
-
#is added to the job_queue. This is not a big deal bc it just means that
|
29
|
-
#the sender's queue will be processed one slot behind it's rightful place.
|
30
|
-
#This does not effect work_order ordering.
|
31
|
-
@redis_pool.with({}) do |redis|
|
32
|
-
@count = redis.incr(COUNTER_KEY)
|
33
|
-
redis.multi do
|
34
|
-
redis.zadd(job_board_key, @count, @sender)
|
35
|
-
redis.rpush(sender_key, Oj.dump(work_order))
|
36
|
-
redis.publish(@channel, WorkerRoulette::JOB_NOTIFICATIONS)
|
37
|
-
end
|
38
|
-
end
|
26
|
+
def counter_key
|
27
|
+
@counter_key ||= WorkerRoulette.counter_key(@namespace)
|
39
28
|
end
|
40
29
|
|
41
|
-
|
42
|
-
|
43
|
-
|
30
|
+
private
|
31
|
+
def sender_key
|
32
|
+
@sender_key = WorkerRoulette.sender_key(sender, @namespace)
|
44
33
|
end
|
45
34
|
|
46
35
|
def default_headers
|
47
36
|
Hash['sender' => sender]
|
48
37
|
end
|
38
|
+
|
39
|
+
def self.lua_enqueue_work_orders
|
40
|
+
<<-HERE
|
41
|
+
local counter_key = KEYS[1]
|
42
|
+
local job_board_key = KEYS[2]
|
43
|
+
local sender_key = KEYS[3]
|
44
|
+
local channel = KEYS[4]
|
45
|
+
|
46
|
+
local work_order = ARGV[1]
|
47
|
+
local job_notification = ARGV[2]
|
48
|
+
|
49
|
+
local function enqueue_work_orders(work_order, job_notification)
|
50
|
+
local result = sender_key .. ' updated'
|
51
|
+
local sender_on_job_board = redis.call('ZSCORE', job_board_key, sender_key)
|
52
|
+
|
53
|
+
if (sender_on_job_board == false) then
|
54
|
+
local count = redis.call('INCR', counter_key)
|
55
|
+
local job_added = redis.call('ZADD',job_board_key, count, sender_key)
|
56
|
+
result = sender_key .. ' added'
|
57
|
+
end
|
58
|
+
|
59
|
+
local work_added = redis.call('RPUSH',sender_key, work_order)
|
60
|
+
local job_board_update = redis.call('PUBLISH', channel, job_notification)
|
61
|
+
return result
|
62
|
+
end
|
63
|
+
|
64
|
+
return enqueue_work_orders(work_order, job_notification)
|
65
|
+
HERE
|
66
|
+
end
|
49
67
|
end
|
50
68
|
end
|
data/lib/worker_roulette/lua.rb
CHANGED
@@ -4,9 +4,7 @@ module WorkerRoulette
|
|
4
4
|
|
5
5
|
def self.call(lua_script, keys_accessed = [], args = [], &callback)
|
6
6
|
WorkerRoulette.tradesman_connection_pool.with do |redis|
|
7
|
-
results =
|
8
|
-
results.callback &callback
|
9
|
-
results.errback {self.eval(redis, lua_script, keys_accessed, args, &callback)}
|
7
|
+
results = evalsha(redis, lua_script, keys_accessed, args, &callback)
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
@@ -24,8 +22,25 @@ module WorkerRoulette
|
|
24
22
|
|
25
23
|
def self.eval(redis, lua_script, keys_accessed, args, &callback)
|
26
24
|
results = redis.eval(lua_script, keys_accessed.size, *keys_accessed, *args)
|
27
|
-
results.callback &callback
|
25
|
+
results.callback &callback if callback
|
28
26
|
results.errback {|err_msg| raise EM::Hiredis::RedisError.new(err_msg)}
|
29
27
|
end
|
28
|
+
|
29
|
+
def self.evalsha(redis, lua_script, keys_accessed, args, &callback)
|
30
|
+
if redis.class == EM::Hiredis::Client
|
31
|
+
results = redis.evalsha(sha(lua_script), keys_accessed.length, *keys_accessed, *args)
|
32
|
+
results.callback &callback if callback
|
33
|
+
results.errback {self.eval(redis, lua_script, keys_accessed, args, &callback)}
|
34
|
+
else
|
35
|
+
begin
|
36
|
+
results = redis.evalsha(sha(lua_script), keys_accessed, args)
|
37
|
+
rescue Redis::CommandError
|
38
|
+
results = redis.eval(lua_script, keys_accessed, args)
|
39
|
+
ensure
|
40
|
+
return callback.call results if callback
|
41
|
+
end
|
42
|
+
end
|
43
|
+
results
|
44
|
+
end
|
30
45
|
end
|
31
46
|
end
|
@@ -8,14 +8,6 @@ module WorkerRoulette
|
|
8
8
|
@channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
|
9
9
|
end
|
10
10
|
|
11
|
-
def job_board_key
|
12
|
-
@job_board_key ||= "#{@namespace + ':' if @namespace}#{WorkerRoulette::JOB_BOARD}"
|
13
|
-
end
|
14
|
-
|
15
|
-
def sender_key
|
16
|
-
@sender_key = "#{@namespace + ':' if @namespace}#{@sender}"
|
17
|
-
end
|
18
|
-
|
19
11
|
def wait_for_work_orders(on_subscribe_callback = nil, &block)
|
20
12
|
@pubsub_pool.with do |redis|
|
21
13
|
redis.subscribe(@channel) do |on|
|
@@ -25,15 +17,12 @@ module WorkerRoulette
|
|
25
17
|
end
|
26
18
|
end
|
27
19
|
|
28
|
-
def work_orders!
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
redis.zrem(job_board_key, sender_key)
|
35
|
-
end
|
36
|
-
((results || []).first || []).map {|work_order| Oj.load(work_order)}
|
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
|
23
|
+
work = (results[1] || []).map {|work_order| WorkerRoulette.load(work_order)}
|
24
|
+
callback.call work if callback
|
25
|
+
work
|
37
26
|
end
|
38
27
|
end
|
39
28
|
|
@@ -41,7 +30,40 @@ module WorkerRoulette
|
|
41
30
|
@pubsub_pool.with {|redis| redis.unsubscribe(@channel)}
|
42
31
|
end
|
43
32
|
|
33
|
+
def job_board_key
|
34
|
+
@job_board_key ||= WorkerRoulette.job_board_key(@namespace)
|
35
|
+
end
|
36
|
+
|
44
37
|
private
|
38
|
+
def sender_key
|
39
|
+
@sender_key = WorkerRoulette.sender_key(@sender, @namespace)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.lua_drain_work_orders
|
43
|
+
<<-HERE
|
44
|
+
local job_board_key = KEYS[1]
|
45
|
+
local empty = KEYS[2]
|
46
|
+
local namespace = ARGV[1]
|
47
|
+
|
48
|
+
local function drain_work_orders(job_board_key, namespace)
|
49
|
+
local sender_key = redis.call('ZRANGE', job_board_key, 0, 0)[1]
|
50
|
+
|
51
|
+
if sender_key == false then
|
52
|
+
return {}
|
53
|
+
end
|
54
|
+
|
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
|
61
|
+
end
|
62
|
+
|
63
|
+
return drain_work_orders(job_board_key, namespace)
|
64
|
+
HERE
|
65
|
+
end
|
66
|
+
|
45
67
|
def get_sender_for_next_job(redis)
|
46
68
|
@sender = (redis.zrange(job_board_key, 0, 0) || []).first.to_s
|
47
69
|
end
|
data/spec/benchmark/perf_test.rb
CHANGED
@@ -12,7 +12,7 @@ WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
|
12
12
|
puts "Redis Connection Pool Size: #{REDIS_CONNECTION_POOL_SIZE}"
|
13
13
|
|
14
14
|
Benchmark.bmbm do |x|
|
15
|
-
x.report "Time to insert and read #{ITERATIONS} large work_orders" do # ~
|
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
16
|
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
17
17
|
ITERATIONS.times do |iteration|
|
18
18
|
sender = 'sender_' + iteration.to_s
|
@@ -33,7 +33,7 @@ EM::Hiredis.reconnect_timeout = 0.01
|
|
33
33
|
WorkerRoulette.tradesman_connection_pool.with {|r| r.flushdb}
|
34
34
|
|
35
35
|
Benchmark.bmbm do |x|
|
36
|
-
x.report "Time for tradesmans to enqueue_work_order and read #{ITERATIONS} large work_orders via pubsub" do # ~
|
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
37
|
WorkerRoulette.start(size: REDIS_CONNECTION_POOL_SIZE, evented: false)
|
38
38
|
ITERATIONS.times do |iteration|
|
39
39
|
p = -> do
|
@@ -9,7 +9,7 @@ describe WorkerRoulette do
|
|
9
9
|
let(:hello_work_order) {Hash['payload' => "hello"]}
|
10
10
|
let(:foreman_work_order) {Hash['payload' => "foreman"]}
|
11
11
|
let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
|
12
|
-
let(:jsonized_work_orders_with_headers) {[
|
12
|
+
let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
|
13
13
|
|
14
14
|
let(:redis) {Redis.new(WorkerRoulette.redis_config)}
|
15
15
|
|
@@ -17,7 +17,7 @@ describe WorkerRoulette do
|
|
17
17
|
WorkerRoulette.start(evented: true)
|
18
18
|
end
|
19
19
|
|
20
|
-
context Foreman do
|
20
|
+
context "Evented Foreman" do
|
21
21
|
let(:subject) {WorkerRoulette.a_foreman(sender)}
|
22
22
|
|
23
23
|
it "should enqueue work" do
|
@@ -33,7 +33,7 @@ describe WorkerRoulette do
|
|
33
33
|
it "should enqueue_work_order two work_orders in the sender's slot in the job board" do
|
34
34
|
subject.enqueue_work_order(work_orders.first) do
|
35
35
|
subject.enqueue_work_order(work_orders.last) do
|
36
|
-
redis.lrange(sender, 0, -1).should == work_orders.map {|m|
|
36
|
+
redis.lrange(sender, 0, -1).should == work_orders.map {|m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
|
37
37
|
done
|
38
38
|
end
|
39
39
|
end
|
@@ -41,7 +41,7 @@ describe WorkerRoulette do
|
|
41
41
|
|
42
42
|
it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the job board" do
|
43
43
|
subject.enqueue_work_order_without_headers(work_orders) do
|
44
|
-
redis.lrange(sender, 0, -1).should == [
|
44
|
+
redis.lrange(sender, 0, -1).should == [WorkerRoulette.dump(work_orders)]
|
45
45
|
done
|
46
46
|
end
|
47
47
|
end
|
@@ -57,7 +57,7 @@ describe WorkerRoulette do
|
|
57
57
|
extra_headers = {'foo' => 'bars'}
|
58
58
|
subject.enqueue_work_order(work_orders, extra_headers) do
|
59
59
|
work_orders_with_headers['headers'].merge!(extra_headers)
|
60
|
-
redis.lrange(sender, 0, -1).should == [
|
60
|
+
redis.lrange(sender, 0, -1).should == [WorkerRoulette.dump(work_orders_with_headers)]
|
61
61
|
done
|
62
62
|
end
|
63
63
|
end
|
@@ -139,7 +139,7 @@ describe WorkerRoulette do
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
context Tradesman do
|
142
|
+
context "Evented Tradesman" do
|
143
143
|
let(:foreman) {WorkerRoulette.a_foreman(sender)}
|
144
144
|
let(:subject) {WorkerRoulette.a_tradesman}
|
145
145
|
|
@@ -266,16 +266,10 @@ describe WorkerRoulette do
|
|
266
266
|
end
|
267
267
|
end
|
268
268
|
|
269
|
-
|
269
|
+
xit "should return a hash with a string in the payload if OJ cannot parse the json" do
|
270
270
|
|
271
271
|
end
|
272
272
|
|
273
|
-
context "Potential Ack Success/Failure for Processing Queues" do
|
274
|
-
xit "should not delete the messages from the queue until they have been processed succcesfully"
|
275
|
-
xit "should checkout a readlock for a queue and put it back when its done processing; lock should expire after 5 minutes?"
|
276
|
-
xit "should retry doing work on a queue 3 times if it is locked (ex backoff)"
|
277
|
-
end
|
278
|
-
|
279
273
|
context "Failure" do
|
280
274
|
it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; done; end
|
281
275
|
end
|
@@ -7,7 +7,7 @@ describe WorkerRoulette do
|
|
7
7
|
let(:hello_work_order) {Hash['payload' => "hello"]}
|
8
8
|
let(:foreman_work_order) {Hash['payload' => "foreman"]}
|
9
9
|
let(:work_orders_with_headers) {default_headers.merge({'payload' => work_orders})}
|
10
|
-
let(:jsonized_work_orders_with_headers) {[
|
10
|
+
let(:jsonized_work_orders_with_headers) {[WorkerRoulette.dump(work_orders_with_headers)]}
|
11
11
|
|
12
12
|
let(:redis) {Redis.new(WorkerRoulette.redis_config)}
|
13
13
|
|
@@ -26,20 +26,15 @@ describe WorkerRoulette do
|
|
26
26
|
subject.sender.should == sender
|
27
27
|
end
|
28
28
|
|
29
|
-
it "should be injected with a raw_redis_client so it can do is work" do
|
30
|
-
Redis.any_instance.should_receive(:rpush)
|
31
|
-
subject.enqueue_work_order(:whatever)
|
32
|
-
end
|
33
|
-
|
34
29
|
it "should enqueue_work_order two work_orders in the sender's slot in the switchboard" do
|
35
|
-
subject.enqueue_work_order(work_orders.first)
|
36
|
-
subject.enqueue_work_order(work_orders.last)
|
37
|
-
redis.lrange(sender, 0, -1).should == work_orders.map {|m|
|
30
|
+
subject.enqueue_work_order(work_orders.first) {}
|
31
|
+
subject.enqueue_work_order(work_orders.last) {}
|
32
|
+
redis.lrange(sender, 0, -1).should == work_orders.map {|m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
|
38
33
|
end
|
39
34
|
|
40
35
|
it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the switchboard" do
|
41
36
|
subject.enqueue_work_order_without_headers(work_orders)
|
42
|
-
redis.lrange(sender, 0, -1).should == [
|
37
|
+
redis.lrange(sender, 0, -1).should == [WorkerRoulette.dump(work_orders)]
|
43
38
|
end
|
44
39
|
|
45
40
|
it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the switchboard" do
|
@@ -51,26 +46,24 @@ describe WorkerRoulette do
|
|
51
46
|
extra_headers = {'foo' => 'bars'}
|
52
47
|
subject.enqueue_work_order(work_orders, extra_headers)
|
53
48
|
work_orders_with_headers['headers'].merge!(extra_headers)
|
54
|
-
redis.lrange(sender, 0, -1).should == [
|
49
|
+
redis.lrange(sender, 0, -1).should == [WorkerRoulette.dump(work_orders_with_headers)]
|
55
50
|
end
|
56
51
|
|
57
52
|
it "should post the sender's id to the job board with an order number" do
|
58
53
|
subject.enqueue_work_order(work_orders.first)
|
59
|
-
|
60
|
-
redis.zrange(subject.job_board_key, 0, -1, with_scores: true).should == [[sender.
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should post the sender_id and work_orders transactionally" do
|
64
|
-
Redis.any_instance.should_receive(:multi)
|
65
|
-
subject.enqueue_work_order(work_orders.first)
|
54
|
+
WorkerRoulette.foreman('other_forman').enqueue_work_order(work_orders.last)
|
55
|
+
redis.zrange(subject.job_board_key, 0, -1, with_scores: true).should == [[sender, 1.0], ["other_forman", 2.0]]
|
66
56
|
end
|
67
57
|
|
68
|
-
it "should generate
|
58
|
+
it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
|
59
|
+
other_forman = WorkerRoulette.foreman('other_forman')
|
69
60
|
redis.get(subject.counter_key).should == nil
|
70
61
|
subject.enqueue_work_order(work_orders.first)
|
71
62
|
redis.get(subject.counter_key).should == "1"
|
72
63
|
subject.enqueue_work_order(work_orders.last)
|
73
|
-
redis.get(subject.counter_key).should == "
|
64
|
+
redis.get(subject.counter_key).should == "1"
|
65
|
+
other_forman.enqueue_work_order(work_orders.last)
|
66
|
+
redis.get(other_forman.counter_key).should == "2"
|
74
67
|
end
|
75
68
|
|
76
69
|
it "should publish a notification that a new job is ready" do
|
@@ -104,11 +97,6 @@ describe WorkerRoulette do
|
|
104
97
|
subject.sender.should == sender
|
105
98
|
end
|
106
99
|
|
107
|
-
it "should be injected with a redis client so it can do its work" do
|
108
|
-
Redis.any_instance.should_receive(:lrange).and_call_original
|
109
|
-
subject.work_orders!
|
110
|
-
end
|
111
|
-
|
112
100
|
it "should drain one set of work_orders from the sender's slot in the switchboard" do
|
113
101
|
subject.work_orders!.should == [work_orders_with_headers]
|
114
102
|
subject.work_orders!.should == []
|
@@ -132,11 +120,6 @@ describe WorkerRoulette do
|
|
132
120
|
redis.zrange(subject.job_board_key, 0, -1).should == [most_recent_sender]
|
133
121
|
end
|
134
122
|
|
135
|
-
it "should get the sender and work_order list transactionally" do
|
136
|
-
Redis.any_instance.should_receive(:multi).and_call_original
|
137
|
-
subject.work_orders!
|
138
|
-
end
|
139
|
-
|
140
123
|
it "should get the work_orders from the next queue when a new job is ready" do
|
141
124
|
subject.work_orders!
|
142
125
|
subject.should_receive(:work_orders!).and_call_original
|
@@ -168,9 +151,6 @@ describe WorkerRoulette do
|
|
168
151
|
end
|
169
152
|
end
|
170
153
|
|
171
|
-
it "should checkout a readlock for a queue and put it back when its done processing; lock should expire after 5 minutes?"
|
172
|
-
it "should eves drop on the job board"
|
173
|
-
|
174
154
|
context "Failure" do
|
175
155
|
it "should not put the sender_id and work_orders back if processing fails bc new work_orders may have been processed while that process failed" do; end
|
176
156
|
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.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -250,7 +250,6 @@ files:
|
|
250
250
|
- README.md
|
251
251
|
- Rakefile
|
252
252
|
- lib/worker_roulette.rb
|
253
|
-
- lib/worker_roulette/a_foreman.rb
|
254
253
|
- lib/worker_roulette/a_tradesman.rb
|
255
254
|
- lib/worker_roulette/foreman.rb
|
256
255
|
- lib/worker_roulette/lua.rb
|
@@ -1,40 +0,0 @@
|
|
1
|
-
require_relative './foreman'
|
2
|
-
module WorkerRoulette
|
3
|
-
class AForeman < Foreman
|
4
|
-
def enqueue_work_order_without_headers(work_order, &callback)
|
5
|
-
Lua.call(self.class.lua_enqueue_work_orders, [COUNTER_KEY, job_board_key, sender_key, @channel],
|
6
|
-
[@sender, Oj.dump(work_order), WorkerRoulette::JOB_NOTIFICATIONS], &callback)
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
def self.lua_enqueue_work_orders
|
11
|
-
<<-HERE
|
12
|
-
local counter_key = KEYS[1]
|
13
|
-
local job_board_key = KEYS[2]
|
14
|
-
local sender_key = KEYS[3]
|
15
|
-
local channel = KEYS[4]
|
16
|
-
|
17
|
-
local sender = ARGV[1]
|
18
|
-
local work_order = ARGV[2]
|
19
|
-
local job_notification = ARGV[3]
|
20
|
-
|
21
|
-
local function enqueue_work_orders(sender, work_order, job_notification)
|
22
|
-
local result = sender .. ' updated'
|
23
|
-
local sender_on_job_board = redis.call('ZSCORE', job_board_key, sender_key)
|
24
|
-
|
25
|
-
if (sender_on_job_board == false) then
|
26
|
-
local count = redis.call('INCR', counter_key)
|
27
|
-
local job_added = redis.call('ZADD',job_board_key, count, sender_key)
|
28
|
-
result = sender .. ' added'
|
29
|
-
end
|
30
|
-
|
31
|
-
local work_added = redis.call('RPUSH',sender_key, work_order)
|
32
|
-
local job_board_update = redis.call('PUBLISH', channel, job_notification)
|
33
|
-
return result
|
34
|
-
end
|
35
|
-
|
36
|
-
return enqueue_work_orders(sender, work_order, job_notification)
|
37
|
-
HERE
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|