worker_roulette 0.1.6 → 0.1.7
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.
- checksums.yaml +8 -8
- data/.gitignore +3 -1
- data/Gemfile +5 -0
- data/lib/worker_roulette/a_tradesman.rb +4 -2
- data/lib/worker_roulette/foreman.rb +33 -30
- data/lib/worker_roulette/tradesman.rb +42 -38
- data/lib/worker_roulette/version.rb +1 -1
- data/lib/worker_roulette.rb +5 -3
- data/spec/integration/evented_worker_roulette_spec.rb +40 -41
- data/spec/integration/worker_roulette_spec.rb +33 -33
- data/spec/unit/evented_readlock_spec.rb +16 -16
- data/spec/unit/lua_spec.rb +8 -8
- data/spec/unit/readlock_spec.rb +18 -20
- data/worker_roulette.gemspec +10 -11
- metadata +8 -22
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
M2EzZmZkYTllZGYwMjA1MDI1MzVmZDU5ODk1ZGRiNzYzMjQzMjQ1OQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NDZmNjhjMTJjZDJhMjJlNzI5MjE2MjllZjFkMGQ1ZDIxODExNTQ4NQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjAyNTBlZDUxNmM5M2Y1MGMwMGYyNzdkNDUzNDEzMGQxNTI0ODY4NTlhY2U1
|
10
|
+
YjRiYjQ1MWI3YzMxNDdmZWM3ZjE1Y2ViZDg1M2FhYTA1ZjhmYzNhYWYwMjY2
|
11
|
+
ZThkODM4ZjY0MmE5NzlmMTdlZTRiMzRhMzUwMWFjMGJkM2NlYTU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ODUxMmQwNWNhOWI5NTBkMmQ2NzFlMzUzY2Q4NjZlZTYzZDE4ZmQ2NGZiODJk
|
14
|
+
ZWNkMzc3ZmUxY2UzOTk4OTJjZjlmNDZkMjU4N2Y3ZGVjMmUzYzIxOTdmMzg2
|
15
|
+
MDJlZjAzNGFiN2JjMGMzNWEwYWVlNmY3OTQ4MjE0NjliY2ViOTE=
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require_relative './tradesman'
|
2
2
|
module WorkerRoulette
|
3
3
|
class ATradesman < Tradesman
|
4
|
+
attr_reader :timer
|
5
|
+
|
4
6
|
def wait_for_work_orders(on_subscribe_callback = nil, &on_message_callback)
|
5
7
|
@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
8
|
@redis_pubsub.on(:subscribe) {|channel, subscription_count| on_subscribe_callback.call(channel, subscription_count) if on_subscribe_callback}
|
@@ -23,8 +25,8 @@ module WorkerRoulette
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
+
private
|
29
|
+
|
28
30
|
def get_messages(message, channel, on_message_callback)
|
29
31
|
return unless on_message_callback
|
30
32
|
work_orders! do |work_orders_1|
|
@@ -2,6 +2,34 @@ module WorkerRoulette
|
|
2
2
|
class Foreman
|
3
3
|
attr_reader :sender
|
4
4
|
|
5
|
+
LUA_ENQUEUE_WORK_ORDERS = <<-HERE
|
6
|
+
local counter_key = KEYS[1]
|
7
|
+
local job_board_key = KEYS[2]
|
8
|
+
local sender_key = KEYS[3]
|
9
|
+
local channel = KEYS[4]
|
10
|
+
|
11
|
+
local work_order = ARGV[1]
|
12
|
+
local job_notification = ARGV[2]
|
13
|
+
local redis_call = redis.call
|
14
|
+
local zscore = 'ZSCORE'
|
15
|
+
local incr = 'INCR'
|
16
|
+
local zadd = 'ZADD'
|
17
|
+
local rpush = 'RPUSH'
|
18
|
+
local publish = 'PUBLISH'
|
19
|
+
|
20
|
+
local function enqueue_work_orders(work_order, job_notification)
|
21
|
+
if (redis_call(zscore, job_board_key, sender_key) == false) then
|
22
|
+
local count = redis_call(incr, counter_key)
|
23
|
+
redis_call(zadd, job_board_key, count, sender_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
redis_call(rpush,sender_key, work_order)
|
27
|
+
redis_call(publish, channel, job_notification)
|
28
|
+
end
|
29
|
+
|
30
|
+
enqueue_work_orders(work_order, job_notification)
|
31
|
+
HERE
|
32
|
+
|
5
33
|
def initialize(sender, redis_pool, namespace = nil)
|
6
34
|
@sender = sender
|
7
35
|
@namespace = namespace
|
@@ -16,7 +44,7 @@ module WorkerRoulette
|
|
16
44
|
|
17
45
|
def enqueue_work_order_without_headers(work_order, &callback)
|
18
46
|
Lua.call(self.class.lua_enqueue_work_orders, [counter_key, job_board_key, sender_key, @channel],
|
19
|
-
|
47
|
+
[WorkerRoulette.dump(work_order), WorkerRoulette::JOB_NOTIFICATIONS], &callback)
|
20
48
|
end
|
21
49
|
|
22
50
|
def job_board_key
|
@@ -27,7 +55,8 @@ module WorkerRoulette
|
|
27
55
|
@counter_key ||= WorkerRoulette.counter_key(@namespace)
|
28
56
|
end
|
29
57
|
|
30
|
-
|
58
|
+
private
|
59
|
+
|
31
60
|
def sender_key
|
32
61
|
@sender_key = WorkerRoulette.sender_key(sender, @namespace)
|
33
62
|
end
|
@@ -37,33 +66,7 @@ module WorkerRoulette
|
|
37
66
|
end
|
38
67
|
|
39
68
|
def self.lua_enqueue_work_orders
|
40
|
-
|
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
|
-
local redis_call = redis.call
|
49
|
-
local zscore = 'ZSCORE'
|
50
|
-
local incr = 'INCR'
|
51
|
-
local zadd = 'ZADD'
|
52
|
-
local rpush = 'RPUSH'
|
53
|
-
local publish = 'PUBLISH'
|
54
|
-
|
55
|
-
local function enqueue_work_orders(work_order, job_notification)
|
56
|
-
if (redis_call(zscore, job_board_key, sender_key) == false) then
|
57
|
-
local count = redis_call(incr, counter_key)
|
58
|
-
redis_call(zadd, job_board_key, count, sender_key)
|
59
|
-
end
|
60
|
-
|
61
|
-
redis_call(rpush,sender_key, work_order)
|
62
|
-
redis_call(publish, channel, job_notification)
|
63
|
-
end
|
64
|
-
|
65
|
-
enqueue_work_orders(work_order, job_notification)
|
66
|
-
HERE
|
69
|
+
LUA_ENQUEUE_WORK_ORDERS
|
67
70
|
end
|
68
71
|
end
|
69
|
-
end
|
72
|
+
end
|
@@ -1,43 +1,8 @@
|
|
1
1
|
module WorkerRoulette
|
2
2
|
class Tradesman
|
3
3
|
attr_reader :last_sender
|
4
|
-
def initialize(client_pool, pubsub_pool, namespace = nil)
|
5
|
-
@client_pool = client_pool
|
6
|
-
@pubsub_pool = pubsub_pool
|
7
|
-
@namespace = namespace
|
8
|
-
@channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
|
9
|
-
end
|
10
|
-
|
11
|
-
def wait_for_work_orders(on_subscribe_callback = nil, &block)
|
12
|
-
@pubsub_pool.with do |redis|
|
13
|
-
redis.subscribe(@channel) do |on|
|
14
|
-
on.subscribe {on_subscribe_callback.call if on_subscribe_callback}
|
15
|
-
on.message {block.call(work_orders! + work_orders!) if block}
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def work_orders!(&callback)
|
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
|
24
|
-
work = (results[1] || []).map {|work_order| WorkerRoulette.load(work_order)}
|
25
|
-
callback.call work if callback
|
26
|
-
work
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def unsubscribe
|
31
|
-
@pubsub_pool.with {|redis| redis.unsubscribe(@channel)}
|
32
|
-
end
|
33
4
|
|
34
|
-
|
35
|
-
@job_board_key ||= WorkerRoulette.job_board_key(@namespace)
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
def self.lua_drain_work_orders
|
40
|
-
<<-HERE
|
5
|
+
LUA_DRAIN_WORK_ORDERS = <<-HERE
|
41
6
|
local empty_string = ""
|
42
7
|
local job_board_key = KEYS[1]
|
43
8
|
local last_sender_key = KEYS[2] or empty_string
|
@@ -88,7 +53,46 @@ module WorkerRoulette
|
|
88
53
|
end
|
89
54
|
|
90
55
|
return drain_work_orders(job_board_key, last_sender_key, empty_string)
|
91
|
-
|
56
|
+
HERE
|
57
|
+
|
58
|
+
def initialize(client_pool, pubsub_pool, namespace = nil)
|
59
|
+
@client_pool = client_pool
|
60
|
+
@pubsub_pool = pubsub_pool
|
61
|
+
@namespace = namespace
|
62
|
+
@channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
|
63
|
+
end
|
64
|
+
|
65
|
+
def wait_for_work_orders(on_subscribe_callback = nil, &block)
|
66
|
+
@pubsub_pool.with do |redis|
|
67
|
+
redis.subscribe(@channel) do |on|
|
68
|
+
on.subscribe {on_subscribe_callback.call if on_subscribe_callback}
|
69
|
+
on.message {block.call(work_orders! + work_orders!) if block}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def work_orders!(&callback)
|
75
|
+
Lua.call(self.class.lua_drain_work_orders, [job_board_key, @last_sender], [nil]) do |results|
|
76
|
+
results ||= []
|
77
|
+
@last_sender = (results.first || '').split(':').first
|
78
|
+
work = (results[1] || []).map {|work_order| WorkerRoulette.load(work_order)}
|
79
|
+
callback.call work if callback
|
80
|
+
work
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def unsubscribe
|
85
|
+
@pubsub_pool.with {|redis| redis.unsubscribe(@channel)}
|
86
|
+
end
|
87
|
+
|
88
|
+
def job_board_key
|
89
|
+
@job_board_key ||= WorkerRoulette.job_board_key(@namespace)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def self.lua_drain_work_orders
|
95
|
+
LUA_DRAIN_WORK_ORDERS
|
92
96
|
end
|
93
97
|
end
|
94
|
-
end
|
98
|
+
end
|
data/lib/worker_roulette.rb
CHANGED
@@ -13,7 +13,7 @@ module WorkerRoulette
|
|
13
13
|
JOB_NOTIFICATIONS = "new_job_ready"
|
14
14
|
|
15
15
|
def self.start(config = {})
|
16
|
-
@redis_config = {host: 'localhost', port: 6379, db: 14, driver: :hiredis, timeout: 5, evented: false, pool_size: 10}.merge(config)
|
16
|
+
@redis_config = { host: 'localhost', port: 6379, db: 14, driver: :hiredis, timeout: 5, evented: false, pool_size: 10 }.merge(config)
|
17
17
|
@pool_config = Hash[size: @redis_config.delete(:pool_size), timeout: @redis_config.delete(:timeout)]
|
18
18
|
@evented = @redis_config.delete(:evented)
|
19
19
|
|
@@ -80,7 +80,9 @@ module WorkerRoulette
|
|
80
80
|
def self.counter_key(sender, namespace = nil)
|
81
81
|
"#{namespace + ':' if namespace}counter_key"
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
|
+
private
|
85
|
+
|
84
86
|
def self.new_redis
|
85
87
|
if @evented
|
86
88
|
require 'eventmachine'
|
@@ -93,7 +95,7 @@ private
|
|
93
95
|
|
94
96
|
def self.new_redis_pubsub
|
95
97
|
if @evented
|
96
|
-
|
98
|
+
new_redis.pubsub
|
97
99
|
else
|
98
100
|
new_redis
|
99
101
|
end
|
@@ -20,19 +20,20 @@ describe WorkerRoulette do
|
|
20
20
|
context "Evented Foreman" do
|
21
21
|
let(:subject) {WorkerRoulette.a_foreman(sender)}
|
22
22
|
|
23
|
-
it "
|
23
|
+
it "enqueues work" do
|
24
24
|
called = false
|
25
|
-
foreman
|
25
|
+
foreman = WorkerRoulette.a_foreman('foreman')
|
26
26
|
foreman.enqueue_work_order('some old fashion work') do |redis_response, stuff|
|
27
27
|
called = true
|
28
28
|
end
|
29
|
-
done(0.1) {called.
|
29
|
+
done(0.1) { expect(called).to be_truthy }
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should enqueue_work_order two work_orders in the sender's slot in the job board" do
|
33
33
|
subject.enqueue_work_order(work_orders.first) do
|
34
34
|
subject.enqueue_work_order(work_orders.last) do
|
35
|
-
|
35
|
+
expected = work_orders.map { |m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) }
|
36
|
+
expect(redis.lrange(sender, 0, -1)).to eq(expected)
|
36
37
|
done
|
37
38
|
end
|
38
39
|
end
|
@@ -40,14 +41,14 @@ describe WorkerRoulette do
|
|
40
41
|
|
41
42
|
it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the job board" do
|
42
43
|
subject.enqueue_work_order_without_headers(work_orders) do
|
43
|
-
redis.lrange(sender, 0, -1).
|
44
|
+
expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
|
44
45
|
done
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
49
|
it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the job board" do
|
49
50
|
subject.enqueue_work_order(work_orders) do
|
50
|
-
redis.lrange(sender, 0, -1).
|
51
|
+
expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
|
51
52
|
done
|
52
53
|
end
|
53
54
|
end
|
@@ -56,7 +57,7 @@ describe WorkerRoulette do
|
|
56
57
|
extra_headers = {'foo' => 'bars'}
|
57
58
|
subject.enqueue_work_order(work_orders, extra_headers) do
|
58
59
|
work_orders_with_headers['headers'].merge!(extra_headers)
|
59
|
-
redis.lrange(sender, 0, -1).
|
60
|
+
expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
|
60
61
|
done
|
61
62
|
end
|
62
63
|
end
|
@@ -66,7 +67,7 @@ describe WorkerRoulette do
|
|
66
67
|
first_foreman.enqueue_work_order('foo') do
|
67
68
|
subject.enqueue_work_order(work_orders.first) do
|
68
69
|
subject.enqueue_work_order(work_orders.last) do
|
69
|
-
redis.zrange(subject.job_board_key, 0, -1, with_scores: true).
|
70
|
+
expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([["first_foreman", 1.0], ["katie_80", 2.0]])
|
70
71
|
done
|
71
72
|
end
|
72
73
|
end
|
@@ -75,13 +76,13 @@ describe WorkerRoulette do
|
|
75
76
|
|
76
77
|
it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
|
77
78
|
first_foreman = WorkerRoulette.a_foreman('first_foreman')
|
78
|
-
redis.get(subject.counter_key).
|
79
|
+
expect(redis.get(subject.counter_key)).to be_nil
|
79
80
|
first_foreman.enqueue_work_order(work_orders.first) do
|
80
|
-
redis.get(subject.counter_key).
|
81
|
+
expect(redis.get(subject.counter_key)).to eq("1")
|
81
82
|
first_foreman.enqueue_work_order(work_orders.last) do
|
82
|
-
redis.get(subject.counter_key).
|
83
|
+
expect(redis.get(subject.counter_key)).to eq("1")
|
83
84
|
subject.enqueue_work_order(work_orders.first) do
|
84
|
-
redis.get(subject.counter_key).
|
85
|
+
expect(redis.get(subject.counter_key)).to eq("2")
|
85
86
|
done
|
86
87
|
end
|
87
88
|
end
|
@@ -93,7 +94,7 @@ describe WorkerRoulette do
|
|
93
94
|
subscriber = WorkerRoulette.new_redis_pubsub
|
94
95
|
subscriber.subscribe(WorkerRoulette::JOB_NOTIFICATIONS) do |message|
|
95
96
|
subscriber.unsubscribe(WorkerRoulette::JOB_NOTIFICATIONS)
|
96
|
-
message.
|
97
|
+
expect(message).to eq(WorkerRoulette::JOB_NOTIFICATIONS)
|
97
98
|
done
|
98
99
|
end.callback { subject.enqueue_work_order(work_orders) }
|
99
100
|
end
|
@@ -106,7 +107,7 @@ describe WorkerRoulette do
|
|
106
107
|
it "should be working on behalf of a sender" do
|
107
108
|
foreman.enqueue_work_order(work_orders) do
|
108
109
|
subject.work_orders! do |r|
|
109
|
-
subject.last_sender.
|
110
|
+
expect(subject.last_sender).to eq(sender)
|
110
111
|
done
|
111
112
|
end
|
112
113
|
end
|
@@ -116,9 +117,9 @@ describe WorkerRoulette do
|
|
116
117
|
it "should drain one set of work_orders from the sender's slot in the job board" do
|
117
118
|
foreman.enqueue_work_order(work_orders) do
|
118
119
|
subject.work_orders! do |r|
|
119
|
-
r.
|
120
|
-
subject.work_orders! do |r| r.
|
121
|
-
|
120
|
+
expect(r).to eq([work_orders_with_headers])
|
121
|
+
subject.work_orders! do |r| expect(r).to be_empty
|
122
|
+
subject.work_orders! {|r| expect(r).to be_empty; done} #does not throw an error if queue is alreay empty
|
122
123
|
end
|
123
124
|
end
|
124
125
|
end
|
@@ -130,19 +131,19 @@ describe WorkerRoulette do
|
|
130
131
|
most_recent_sender = 'most_recent_sender'
|
131
132
|
most_recent_foreman = WorkerRoulette.a_foreman(most_recent_sender)
|
132
133
|
most_recent_foreman.enqueue_work_order(work_orders) do
|
133
|
-
redis.zrange(subject.job_board_key, 0, -1).
|
134
|
-
subject.work_orders! { redis.zrange(subject.job_board_key, 0, -1).
|
134
|
+
expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
|
135
|
+
subject.work_orders! { expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([most_recent_sender]); done }
|
135
136
|
end
|
136
137
|
end
|
137
138
|
end
|
138
139
|
|
139
140
|
it "should get the work_orders from the next queue when a new job is ready" do
|
140
|
-
subject.
|
141
|
+
expect(subject).to receive(:work_orders!).twice.and_call_original
|
141
142
|
publish = proc {foreman.enqueue_work_order(work_orders)}
|
142
143
|
|
143
144
|
subject.wait_for_work_orders(publish) do |redis_work_orders, message, channel|
|
144
|
-
redis_work_orders.
|
145
|
-
subject.last_sender.
|
145
|
+
expect(redis_work_orders).to eq([work_orders_with_headers])
|
146
|
+
expect(subject.last_sender).to be_nil
|
146
147
|
done
|
147
148
|
end
|
148
149
|
end
|
@@ -160,19 +161,19 @@ describe WorkerRoulette do
|
|
160
161
|
good_publish = proc {good_foreman.enqueue_work_order('some old fashion work')}
|
161
162
|
bad_publish = proc {bad_foreman.enqueue_work_order('evil biddings you should not carry out')}
|
162
163
|
|
163
|
-
tradesman.
|
164
|
-
evil_tradesman.
|
164
|
+
expect(tradesman).to receive(:work_orders!).twice.and_call_original
|
165
|
+
expect(evil_tradesman).to receive(:work_orders!).twice.and_call_original
|
165
166
|
|
166
167
|
#They are double subscribing; is it possible that it is the connection pool?
|
167
168
|
|
168
169
|
tradesman.wait_for_work_orders(good_publish) do |good_work|
|
169
|
-
good_work.to_s.
|
170
|
-
good_work.to_s.
|
170
|
+
expect(good_work.to_s).to match("old fashion")
|
171
|
+
expect(good_work.to_s).not_to match("evil")
|
171
172
|
end
|
172
173
|
|
173
174
|
evil_tradesman.wait_for_work_orders(bad_publish) do |bad_work|
|
174
|
-
bad_work.to_s.
|
175
|
-
bad_work.to_s.
|
175
|
+
expect(bad_work.to_s).not_to match("old fashion")
|
176
|
+
expect(bad_work.to_s).to match("evil")
|
176
177
|
end
|
177
178
|
|
178
179
|
done(0.2)
|
@@ -183,51 +184,49 @@ describe WorkerRoulette do
|
|
183
184
|
subject.wait_for_work_orders(publish) do |redis_work_orders, message, channel|
|
184
185
|
subject.unsubscribe {done}
|
185
186
|
end
|
186
|
-
EM::Hiredis::PubsubClient.
|
187
|
+
expect_any_instance_of(EM::Hiredis::PubsubClient).to receive(:close_connection).and_call_original
|
187
188
|
end
|
188
189
|
|
189
190
|
it "should periodically (random time between 20 and 25 seconds?) poll the job board for new work, in case it missed a notification" do
|
190
|
-
EM::PeriodicTimer.
|
191
|
+
expect(EM::PeriodicTimer).to receive(:new) {|time| expect(time).to be_within(2.5).of(22.5)}
|
191
192
|
publish = proc {foreman.enqueue_work_order('foo')}
|
192
193
|
subject.wait_for_work_orders(publish) {done}
|
193
194
|
end
|
194
195
|
|
195
|
-
|
196
|
+
pending "cancels the old timer when the on_message callback is called" do
|
196
197
|
publish = proc {foreman.enqueue_work_order('foo')}
|
197
198
|
subject.wait_for_work_orders(publish) do
|
198
|
-
subject.send(:timer).
|
199
|
+
expect(subject.send(:timer)).to receive(:cancel).and_call_original
|
199
200
|
done
|
200
201
|
end
|
201
202
|
end
|
202
203
|
|
203
204
|
it "should pull off work orders for more than one sender" do
|
204
|
-
tradesman
|
205
|
+
tradesman = WorkerRoulette.a_tradesman('good_channel')
|
205
206
|
|
206
|
-
good_foreman
|
207
|
-
lazy_foreman
|
207
|
+
good_foreman = WorkerRoulette.a_foreman('good_foreman', 'good_channel')
|
208
|
+
lazy_foreman = WorkerRoulette.a_foreman('lazy_foreman', 'good_channel')
|
208
209
|
|
209
210
|
got_good = false
|
210
211
|
got_lazy = false
|
211
212
|
good_foreman.enqueue_work_order('do good work') do
|
212
213
|
tradesman.work_orders! do |r|
|
213
214
|
got_good = true
|
214
|
-
r.first['payload'].
|
215
|
+
expect(r.first['payload']).to eq('do good work')
|
215
216
|
end
|
216
217
|
end
|
217
218
|
lazy_foreman.enqueue_work_order('just get it done') do
|
218
219
|
tradesman.work_orders! do |r|
|
219
220
|
got_lazy = true
|
220
|
-
r.first['payload'].
|
221
|
+
expect(r.first['payload']).to eq('just get it done')
|
221
222
|
end
|
222
223
|
end
|
223
224
|
|
224
|
-
done(0.2) {(got_good && got_lazy).
|
225
|
+
done(0.2) {expect(got_good && got_lazy).to eq(true)}
|
225
226
|
end
|
226
227
|
end
|
227
228
|
|
228
|
-
|
229
|
-
|
230
|
-
end
|
229
|
+
pending "should return a hash with a string in the payload if OJ cannot parse the json"
|
231
230
|
|
232
231
|
context "Failure" do
|
233
232
|
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
|
@@ -16,54 +16,54 @@ describe WorkerRoulette do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should exist" do
|
19
|
-
WorkerRoulette.
|
19
|
+
expect(WorkerRoulette).not_to be_nil
|
20
20
|
end
|
21
21
|
|
22
22
|
context Foreman do
|
23
23
|
let(:subject) {WorkerRoulette.foreman(sender)}
|
24
24
|
|
25
25
|
it "should be working on behalf of a sender" do
|
26
|
-
subject.sender.
|
26
|
+
expect(subject.sender).to eq(sender)
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should enqueue_work_order two work_orders in the sender's slot in the switchboard" do
|
30
30
|
subject.enqueue_work_order(work_orders.first) {}
|
31
31
|
subject.enqueue_work_order(work_orders.last) {}
|
32
|
-
redis.lrange(sender, 0, -1).
|
32
|
+
expect(redis.lrange(sender, 0, -1)).to eq(work_orders.map {|m| WorkerRoulette.dump(default_headers.merge({'payload' => m})) })
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should enqueue_work_order an array of work_orders without headers in the sender's slot in the switchboard" do
|
36
36
|
subject.enqueue_work_order_without_headers(work_orders)
|
37
|
-
redis.lrange(sender, 0, -1).
|
37
|
+
expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders)])
|
38
38
|
end
|
39
39
|
|
40
40
|
it "should enqueue_work_order an array of work_orders with default headers in the sender's slot in the switchboard" do
|
41
41
|
subject.enqueue_work_order(work_orders)
|
42
|
-
redis.lrange(sender, 0, -1).
|
42
|
+
expect(redis.lrange(sender, 0, -1)).to eq(jsonized_work_orders_with_headers)
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should enqueue_work_order an array of work_orders with additional headers in the sender's slot in the switchboard" do
|
46
46
|
extra_headers = {'foo' => 'bars'}
|
47
47
|
subject.enqueue_work_order(work_orders, extra_headers)
|
48
48
|
work_orders_with_headers['headers'].merge!(extra_headers)
|
49
|
-
redis.lrange(sender, 0, -1).
|
49
|
+
expect(redis.lrange(sender, 0, -1)).to eq([WorkerRoulette.dump(work_orders_with_headers)])
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should post the sender's id to the job board with an order number" do
|
53
53
|
subject.enqueue_work_order(work_orders.first)
|
54
54
|
WorkerRoulette.foreman('other_forman').enqueue_work_order(work_orders.last)
|
55
|
-
redis.zrange(subject.job_board_key, 0, -1, with_scores: true).
|
55
|
+
expect(redis.zrange(subject.job_board_key, 0, -1, with_scores: true)).to eq([[sender, 1.0], ["other_forman", 2.0]])
|
56
56
|
end
|
57
57
|
|
58
58
|
it "should generate a monotically increasing score for senders not on the job board, but not for senders already there" do
|
59
59
|
other_forman = WorkerRoulette.foreman('other_forman')
|
60
|
-
redis.get(subject.counter_key).
|
60
|
+
expect(redis.get(subject.counter_key)).to be_nil
|
61
61
|
subject.enqueue_work_order(work_orders.first)
|
62
|
-
redis.get(subject.counter_key).
|
62
|
+
expect(redis.get(subject.counter_key)).to eq("1")
|
63
63
|
subject.enqueue_work_order(work_orders.last)
|
64
|
-
redis.get(subject.counter_key).
|
64
|
+
expect(redis.get(subject.counter_key)).to eq("1")
|
65
65
|
other_forman.enqueue_work_order(work_orders.last)
|
66
|
-
redis.get(other_forman.counter_key).
|
66
|
+
expect(redis.get(other_forman.counter_key)).to eq("2")
|
67
67
|
end
|
68
68
|
|
69
69
|
it "should publish a notification that a new job is ready" do
|
@@ -80,7 +80,7 @@ describe WorkerRoulette do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
result.
|
83
|
+
expect(result).to eq(WorkerRoulette::JOB_NOTIFICATIONS)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -93,27 +93,27 @@ describe WorkerRoulette do
|
|
93
93
|
end
|
94
94
|
|
95
95
|
it "should have a last sender if it found messages" do
|
96
|
-
subject.work_orders!.length.
|
97
|
-
subject.last_sender.
|
96
|
+
expect(subject.work_orders!.length).to eq(1)
|
97
|
+
expect(subject.last_sender).to eq(sender)
|
98
98
|
end
|
99
99
|
|
100
100
|
it "should not have a last sender if it found no messages" do
|
101
|
-
subject.work_orders!.length.
|
102
|
-
subject.work_orders!.length.
|
103
|
-
subject.last_sender.
|
101
|
+
expect(subject.work_orders!.length).to eq(1)
|
102
|
+
expect(subject.work_orders!.length).to eq(0)
|
103
|
+
expect(subject.last_sender).to be_nil
|
104
104
|
end
|
105
105
|
|
106
106
|
it "should drain one set of work_orders from the sender's slot in the switchboard" do
|
107
|
-
subject.work_orders
|
108
|
-
subject.work_orders
|
109
|
-
subject.work_orders
|
107
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers])
|
108
|
+
expect(subject.work_orders!).to be_empty
|
109
|
+
expect(subject.work_orders!).to be_empty #does not throw an error if queue is already empty
|
110
110
|
end
|
111
111
|
|
112
112
|
it "should drain all the work_orders from the sender's slot in the switchboard" do
|
113
113
|
foreman.enqueue_work_order(work_orders)
|
114
|
-
subject.work_orders
|
115
|
-
subject.work_orders
|
116
|
-
subject.work_orders
|
114
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers, work_orders_with_headers])
|
115
|
+
expect(subject.work_orders!).to be_empty
|
116
|
+
expect(subject.work_orders!).to be_empty #does not throw an error if queue is already empty
|
117
117
|
end
|
118
118
|
|
119
119
|
it "should take the oldest sender off the job board (FIFO)" do
|
@@ -121,26 +121,26 @@ describe WorkerRoulette do
|
|
121
121
|
most_recent_sender = 'most_recent_sender'
|
122
122
|
most_recent_foreman = WorkerRoulette.foreman(most_recent_sender)
|
123
123
|
most_recent_foreman.enqueue_work_order(work_orders)
|
124
|
-
redis.zrange(subject.job_board_key, 0, -1).
|
124
|
+
expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([oldest_sender, most_recent_sender])
|
125
125
|
subject.work_orders!
|
126
|
-
redis.zrange(subject.job_board_key, 0, -1).
|
126
|
+
expect(redis.zrange(subject.job_board_key, 0, -1)).to eq([most_recent_sender])
|
127
127
|
end
|
128
128
|
|
129
129
|
it "should get the work_orders from the next queue when a new job is ready" do
|
130
130
|
subject.work_orders!
|
131
|
-
subject.
|
131
|
+
expect(subject).to receive(:work_orders!).twice.and_call_original
|
132
132
|
|
133
133
|
publisher = -> {foreman.enqueue_work_order(work_orders); subject.unsubscribe}
|
134
134
|
|
135
135
|
subject.wait_for_work_orders(publisher) do |redis_work_orders|
|
136
|
-
redis_work_orders.
|
137
|
-
subject.last_sender.
|
136
|
+
expect(redis_work_orders).to eq([work_orders_with_headers])
|
137
|
+
expect(subject.last_sender).to be_nil
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
141
|
it "should publish and subscribe on custom channels" do
|
142
142
|
tradesman = WorkerRoulette.tradesman('good_channel')
|
143
|
-
tradesman.
|
143
|
+
expect(tradesman).to receive(:work_orders!).twice.and_call_original
|
144
144
|
|
145
145
|
good_foreman = WorkerRoulette.foreman('foreman', 'good_channel')
|
146
146
|
bad_foreman = WorkerRoulette.foreman('foreman', 'bad_channel')
|
@@ -153,9 +153,9 @@ describe WorkerRoulette do
|
|
153
153
|
end
|
154
154
|
|
155
155
|
tradesman.wait_for_work_orders(publish) do |work|
|
156
|
-
work.to_s.
|
157
|
-
work.to_s.
|
158
|
-
tradesman.last_sender.
|
156
|
+
expect(work.to_s).to match("some old fashion work")
|
157
|
+
expect(work.to_s).not_to match("evil")
|
158
|
+
expect(tradesman.last_sender).to be_nil
|
159
159
|
end
|
160
160
|
end
|
161
161
|
|
@@ -169,7 +169,7 @@ describe WorkerRoulette do
|
|
169
169
|
Thread.new {WorkerRoulette.tradesman_connection_pool.with {|pooled_redis| pooled_redis.get("foo")}}
|
170
170
|
end.each(&:join)
|
171
171
|
WorkerRoulette.tradesman_connection_pool.with do |pooled_redis|
|
172
|
-
pooled_redis.info["connected_clients"].to_i.
|
172
|
+
expect(pooled_redis.info["connected_clients"].to_i).to be > (WorkerRoulette.pool_size)
|
173
173
|
end
|
174
174
|
end
|
175
175
|
|
@@ -24,14 +24,14 @@ module WorkerRoulette
|
|
24
24
|
|
25
25
|
it "should lock a queue when it reads from it" do
|
26
26
|
evented_readlock_preconditions do
|
27
|
-
redis.get(lock_key).
|
27
|
+
expect(redis.get(lock_key)).not_to be_nil
|
28
28
|
done
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should set the lock to expire in 1 second" do
|
33
33
|
evented_readlock_preconditions do
|
34
|
-
redis.ttl(lock_key).
|
34
|
+
expect(redis.ttl(lock_key)).to eq(1)
|
35
35
|
done
|
36
36
|
end
|
37
37
|
end
|
@@ -39,7 +39,7 @@ module WorkerRoulette
|
|
39
39
|
it "should not read a locked queue" do
|
40
40
|
evented_readlock_preconditions do
|
41
41
|
foreman.enqueue_work_order(work_orders) do #locked
|
42
|
-
subject_two.work_orders! {|work
|
42
|
+
subject_two.work_orders! { |work| expect(work).to be_empty; done}
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -48,7 +48,7 @@ module WorkerRoulette
|
|
48
48
|
evented_readlock_preconditions do
|
49
49
|
foreman.enqueue_work_order(work_orders) do #locked
|
50
50
|
number_two.enqueue_work_order(work_orders) do #unlocked
|
51
|
-
subject_two.work_orders!{|work| work.first['headers']['sender'].
|
51
|
+
subject_two.work_orders!{|work| expect(work.first['headers']['sender']).to eq('number_two'); done}
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -57,10 +57,10 @@ module WorkerRoulette
|
|
57
57
|
it "should release its last lock when it asks for its next work order from another sender" do
|
58
58
|
evented_readlock_preconditions do
|
59
59
|
number_two.enqueue_work_order(work_orders) do #unlocked
|
60
|
-
subject.last_sender.
|
60
|
+
expect(subject.last_sender).to eq(sender)
|
61
61
|
subject.work_orders! do |work|
|
62
|
-
work.first['headers']['sender'].
|
63
|
-
redis.get(lock_key).
|
62
|
+
expect(work.first['headers']['sender']).to eq('number_two')
|
63
|
+
expect(redis.get(lock_key)).to be_nil
|
64
64
|
done
|
65
65
|
end
|
66
66
|
end
|
@@ -71,9 +71,9 @@ module WorkerRoulette
|
|
71
71
|
evented_readlock_preconditions do
|
72
72
|
foreman.enqueue_work_order(work_orders) do #locked
|
73
73
|
subject.work_orders! do |work|
|
74
|
-
work.
|
75
|
-
subject.last_sender.
|
76
|
-
redis.get(lock_key).
|
74
|
+
expect(work).to eq([work_orders_with_headers])
|
75
|
+
expect(subject.last_sender).to eq(sender)
|
76
|
+
expect(redis.get(lock_key)).not_to be_nil
|
77
77
|
done
|
78
78
|
end
|
79
79
|
end
|
@@ -83,11 +83,11 @@ module WorkerRoulette
|
|
83
83
|
it "should not take out another lock if there is no work to do" do
|
84
84
|
evented_readlock_preconditions do
|
85
85
|
foreman.enqueue_work_order(work_orders) do #locked
|
86
|
-
subject.work_orders! do |
|
87
|
-
|
86
|
+
subject.work_orders! do |work_order|
|
87
|
+
expect(work_order).to eq([work_orders_with_headers])
|
88
88
|
subject.work_orders! do |work|
|
89
|
-
work.
|
90
|
-
redis.get(lock_key).
|
89
|
+
expect(work).to be_empty
|
90
|
+
expect(redis.get(lock_key)).to be_nil
|
91
91
|
done
|
92
92
|
end
|
93
93
|
end
|
@@ -99,9 +99,9 @@ module WorkerRoulette
|
|
99
99
|
def evented_readlock_preconditions(&spec_block)
|
100
100
|
foreman.enqueue_work_order(work_orders) do
|
101
101
|
subject.work_orders! do |work|
|
102
|
-
work.
|
102
|
+
expect(work).to eq([work_orders_with_headers])
|
103
103
|
spec_block.call
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
107
|
-
end
|
107
|
+
end
|
data/spec/unit/lua_spec.rb
CHANGED
@@ -17,22 +17,22 @@ module WorkerRoulette
|
|
17
17
|
it "should load and call a lua script" do
|
18
18
|
lua_script = 'return redis.call("SET", KEYS[1], ARGV[1])'
|
19
19
|
Lua.call(lua_script, ['foo'], ['daddy']) do |result|
|
20
|
-
Lua.cache.keys.first.
|
21
|
-
Lua.cache.values.first.
|
22
|
-
result.
|
20
|
+
expect(Lua.cache.keys.first).to eq(lua_script)
|
21
|
+
expect(Lua.cache.values.first).to eq(Digest::SHA1.hexdigest(lua_script))
|
22
|
+
expect(result).to eq("OK")
|
23
23
|
done
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
it "should send a sha instead of a script once the script has been cached" do
|
28
28
|
lua_script = 'return KEYS'
|
29
|
-
Lua.
|
29
|
+
expect(Lua).to receive(:eval).and_call_original
|
30
30
|
|
31
31
|
Lua.call(lua_script) do |result|
|
32
|
+
expect(Lua).not_to receive(:eval)
|
32
33
|
|
33
|
-
Lua.
|
34
|
-
|
35
|
-
result.should == []
|
34
|
+
Lua.call(lua_script) do |inner_result|
|
35
|
+
expect(inner_result).to be_empty
|
36
36
|
done
|
37
37
|
end
|
38
38
|
end
|
@@ -46,4 +46,4 @@ module WorkerRoulette
|
|
46
46
|
done
|
47
47
|
end
|
48
48
|
end
|
49
|
-
end
|
49
|
+
end
|
data/spec/unit/readlock_spec.rb
CHANGED
@@ -19,56 +19,54 @@ module WorkerRoulette
|
|
19
19
|
redis.script(:flush)
|
20
20
|
redis.flushdb
|
21
21
|
foreman.enqueue_work_order(work_orders)
|
22
|
-
subject.work_orders
|
22
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers])
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should lock a queue when it reads from it" do
|
26
|
-
redis.get(lock_key).
|
26
|
+
expect(redis.get(lock_key)).not_to be_nil
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should set the lock to expire in 1 second" do
|
30
|
-
redis.ttl(lock_key).
|
30
|
+
expect(redis.ttl(lock_key)).to eq(1)
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should not read a locked queue" do
|
34
34
|
foreman.enqueue_work_order(work_orders) #locked
|
35
|
-
subject_two.work_orders
|
35
|
+
expect(subject_two.work_orders!).to be_empty
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should read from the first available queue that is not locked" do
|
39
39
|
foreman.enqueue_work_order(work_orders) #locked
|
40
40
|
number_two.enqueue_work_order(work_orders) #unlocked
|
41
|
-
subject_two.work_orders!.first['headers']['sender'].
|
41
|
+
expect(subject_two.work_orders!.first['headers']['sender']).to eq('number_two')
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should release its previous lock when it asks for work from another sender" do
|
45
45
|
number_two.enqueue_work_order(work_orders) #unlocked
|
46
|
-
subject.last_sender.
|
47
|
-
subject.work_orders!.first['headers']['sender'].
|
48
|
-
redis.get(lock_key).
|
46
|
+
expect(subject.last_sender).to eq(sender)
|
47
|
+
expect(subject.work_orders!.first['headers']['sender']).to eq('number_two')
|
48
|
+
expect(redis.get(lock_key)).to be_nil
|
49
49
|
end
|
50
50
|
|
51
51
|
it "should not release its lock when it asks for work from the same sender" do
|
52
52
|
foreman.enqueue_work_order(work_orders) #locked
|
53
|
-
subject.work_orders
|
54
|
-
subject.last_sender.
|
53
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers])
|
54
|
+
expect(subject.last_sender).to eq(sender)
|
55
55
|
|
56
56
|
foreman.enqueue_work_order(work_orders) #locked
|
57
|
-
subject.work_orders
|
58
|
-
subject.last_sender.
|
57
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers])
|
58
|
+
expect(subject.last_sender).to eq(sender)
|
59
59
|
|
60
|
-
redis.get(lock_key).
|
60
|
+
expect(redis.get(lock_key)).not_to be_nil
|
61
61
|
end
|
62
62
|
|
63
63
|
it "should release its previous lock if there is no work to do from the same sender" do
|
64
64
|
foreman.enqueue_work_order(work_orders) #locked
|
65
|
-
subject.work_orders
|
66
|
-
subject.work_orders
|
67
|
-
redis.get(lock_key).
|
65
|
+
expect(subject.work_orders!).to eq([work_orders_with_headers])
|
66
|
+
expect(subject.work_orders!).to be_empty
|
67
|
+
expect(redis.get(lock_key)).to be_nil
|
68
68
|
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
70
|
+
pending "pubsub should clean up one contention or remove the lock on the same sender queue automaticly"
|
73
71
|
end
|
74
|
-
end
|
72
|
+
end
|
data/worker_roulette.gemspec
CHANGED
@@ -4,18 +4,18 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'worker_roulette/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'worker_roulette'
|
8
8
|
spec.version = WorkerRoulette::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description = %q{
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.homepage =
|
9
|
+
spec.authors = ['Paul Saieg']
|
10
|
+
spec.email = ['classicist@gmail.com']
|
11
|
+
spec.description = %q{Pub Sub Queue for Redis that ensures ordered processing}
|
12
|
+
spec.summary = %q{Pub Sub Queue for Redis that ensures ordered processing}
|
13
|
+
spec.homepage = 'https://github.com/nexiahome/worker_roulette'
|
14
14
|
|
15
15
|
spec.files = `git ls-files`.split($/)
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.test_files = spec.files.grep(%r{^(spec)/})
|
18
|
-
spec.require_paths = [
|
18
|
+
spec.require_paths = ['lib']
|
19
19
|
|
20
20
|
spec.add_dependency 'oj'
|
21
21
|
spec.add_dependency 'redis', '~> 3.0.7'
|
@@ -24,14 +24,13 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'connection_pool'
|
25
25
|
spec.add_dependency 'eventmachine', '~> 1.0.3'
|
26
26
|
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency 'rspec'
|
27
|
+
spec.add_development_dependency 'bundler'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
30
30
|
spec.add_development_dependency 'pry-debugger'
|
31
31
|
spec.add_development_dependency 'simplecov'
|
32
32
|
spec.add_development_dependency 'simplecov-rcov'
|
33
33
|
spec.add_development_dependency 'rspec_junit_formatter'
|
34
|
-
spec.add_development_dependency 'evented-spec'
|
35
34
|
spec.add_development_dependency 'guard'
|
36
35
|
spec.add_development_dependency 'guard-rspec'
|
37
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Saieg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -126,16 +126,16 @@ dependencies:
|
|
126
126
|
name: rspec
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ~>
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 3.0.0
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ~>
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 3.0.0
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: pry-debugger
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -192,20 +192,6 @@ dependencies:
|
|
192
192
|
- - ! '>='
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '0'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: evented-spec
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - ! '>='
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '0'
|
202
|
-
type: :development
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - ! '>='
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '0'
|
209
195
|
- !ruby/object:Gem::Dependency
|
210
196
|
name: guard
|
211
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -234,7 +220,7 @@ dependencies:
|
|
234
220
|
- - ! '>='
|
235
221
|
- !ruby/object:Gem::Version
|
236
222
|
version: '0'
|
237
|
-
description:
|
223
|
+
description: Pub Sub Queue for Redis that ensures ordered processing
|
238
224
|
email:
|
239
225
|
- classicist@gmail.com
|
240
226
|
executables: []
|
@@ -287,7 +273,7 @@ rubyforge_project:
|
|
287
273
|
rubygems_version: 2.2.2
|
288
274
|
signing_key:
|
289
275
|
specification_version: 4
|
290
|
-
summary:
|
276
|
+
summary: Pub Sub Queue for Redis that ensures ordered processing
|
291
277
|
test_files:
|
292
278
|
- spec/benchmark/perf_test.rb
|
293
279
|
- spec/helpers/.gitkeep
|