worker_roulette 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,36 +4,68 @@ WorkerRoulette is designed to allow large numbers of unique devices, processes,
4
4
 
5
5
  WorkerRoulette lets you have thousands of competing consumers (distrubted over as many machines as you'd like) processing ordered messages from millions of totally unknown message providers. It does all this and ensures that the messages sent from each message provider are processed in exactly the order it sent them.
6
6
 
7
- ## Usage
8
- size_of_connection_pool = 100
9
- redis_config = {host: 'localhost', timeout: 5, db: 1}
10
-
11
- WorkerRoulette.start(size_of_connection_pool, redis_config)
12
-
13
- sender_id = :shady
14
- foreman = WorkerRoulette.foreman(sender_id)
15
- foreman.enqueue_work_order(['hello', 'foreman'])
16
-
17
- tradesman = WorkerRoulette.tradesman
18
- messages = tradesman.work_orders! #drain the queue of the next available sender
19
- messages.first # => ['hello', 'foreman']
20
-
21
- other_sender_id = :the_real_slim_shady
22
- other_foreman = WorkerRoulette.foreman(other_sender_id)
23
- other_foreman.enqueue_work_order({'can you get me' => 'the number nine?'})
24
-
25
- messages = tradesman.work_orders! #drain the queue of the next available sender
26
- messages.first # => {'can you get me' => 'the number nine?'}
27
-
28
- on_subscribe_callback = -> do
29
- puts "Huzzah! We're listening!"
30
- foreman.enqueue_work_order('will I see you later?')
31
- foreman.enqueue_work_order('can you give me back my dime?')
32
- end
33
-
34
- tradesman.wait_for_work_orders(on_subscribe_callback) do |messages| #drain the queue of the next available sender
35
- messages # => ['will I see you later', 'can you give me back my dime?']
36
- end
7
+ ## General Usage
8
+ ```ruby
9
+ size_of_connection_pool = 100
10
+ redis_config = {host: 'localhost', timeout: 5, db: 1}
11
+
12
+ #Start it up
13
+ WorkerRoulette.start(size_of_connection_pool, redis_config)
14
+
15
+ #Enqueue some work
16
+ sender_id = :shady
17
+ foreman = WorkerRoulette.foreman(sender_id)
18
+ foreman.enqueue_work_order(['hello', 'foreman'])
19
+
20
+ #Pull it off
21
+ tradesman = WorkerRoulette.tradesman
22
+ messages = tradesman.work_orders! #drain the queue of the next available sender
23
+ messages.first # => ['hello', 'foreman']
24
+
25
+ #Enqueue some more from someone else
26
+ other_sender_id = :the_real_slim_shady
27
+ other_foreman = WorkerRoulette.foreman(other_sender_id)
28
+ other_foreman.enqueue_work_order({'can you get me' => 'the number nine?'})
29
+
30
+ #Have the same worker pull that off
31
+ messages = tradesman.work_orders! #drain the queue of the next available sender
32
+ messages.first # => {'can you get me' => 'the number nine?'}
33
+
34
+ #Have your workers wait for work to come in
35
+ on_subscribe_callback = -> do
36
+ puts "Huzzah! We're listening!"
37
+ foreman.enqueue_work_order('will I see you later?')
38
+ foreman.enqueue_work_order('can you give me back my dime?')
39
+ end
40
+
41
+
42
+ #And they will pull it off as it comes, as long as it comes
43
+ #(This is a blocking operation, so it is best in Threads or EventMachine.next_tick)
44
+ tradesman.wait_for_work_orders(on_subscribe_callback) do |messages| #drain the queue of the next available sender
45
+ messages # => ['will I see you later', 'can you give me back my dime?']
46
+ end
47
+ ```
48
+
49
+ ## Channels
50
+ You can also namespace your work orders over a channel, in case you have several sorts of competing consumers who should not step on each other's toes:
51
+ ```ruby
52
+ tradesman = WorkerRoulette.tradesman('good_channel')
53
+ tradesman.should_receive(:work_orders!).and_call_original
54
+
55
+ good_foreman = WorkerRoulette.foreman('foreman', 'good_channel')
56
+ bad_foreman = WorkerRoulette.foreman('foreman', 'bad_channel')
57
+
58
+ publish = -> do
59
+ good_foreman.enqueue_work_order('some old fashion work')
60
+ bad_foreman.enqueue_work_order('evil biddings you should not carry out') #channels let us ignore his evil orders
61
+ end
62
+
63
+ tradesman.wait_for_work_orders(publish) do |work|
64
+ work.to_s.should match("some old fashion work") #only got the work from the good foreman
65
+ tradesman.unsubscribe
66
+ end
67
+
68
+ ```
37
69
 
38
70
  ##Caveat Emptor
39
71
  While WorkerRoulette does promise to keep the messages of each consumer processed in order by competing consumers, it does NOT guarantee the order in which the queues themselves will be processed. In general, work is processed in a FIFO order, but for performance reasons this has been left a loose FIFO. For example, if Abdul enqueue_work_orders some ordered messages ('1', '2', and '3') and then so do Mark and Wanda, Mark's messages may be processed first, then it would likely be Abdul's, and then Wanda's. However, even though Mark jumped the line, Abdul's messages will still be processed the order he enqueue_work_orderd them ('1', '2', then '3').
@@ -3,17 +3,23 @@ module WorkerRoulette
3
3
  attr_reader :sender
4
4
  COUNTER_KEY = 'counter_key'
5
5
 
6
- def initialize(sender, redis_pool)
7
- @sender = sender
6
+ def initialize(sender, redis_pool, namespace = nil)
7
+ @sender = sender
8
+ @namespace = namespace
8
9
  @redis_pool = redis_pool
10
+ @channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
9
11
  end
10
12
 
11
13
  def job_board_key
12
- WorkerRoulette::JOB_BOARD
14
+ @job_board_key ||= "#{@namespace + ':' if @namespace}#{WorkerRoulette::JOB_BOARD}"
15
+ end
16
+
17
+ def sender_key
18
+ @sender_key ||= "#{@namespace + ':' if @namespace}#{@sender}"
13
19
  end
14
20
 
15
21
  def counter_key
16
- COUNTER_KEY
22
+ @counter_key ||= "#{@namespace + ':' if @namespace}#{COUNTER_KEY}"
17
23
  end
18
24
 
19
25
  def enqueue_work_order_without_headers(work_order)
@@ -25,9 +31,9 @@ module WorkerRoulette
25
31
  @redis_pool.with({}) do |redis|
26
32
  @count = redis.incr(COUNTER_KEY)
27
33
  redis.multi do
28
- redis.zadd(WorkerRoulette::JOB_BOARD, @count, sender)
29
- redis.rpush(sender, Oj.dump(work_order))
30
- redis.publish(WorkerRoulette::JOB_NOTIFICATIONS, WorkerRoulette::JOB_NOTIFICATIONS)
34
+ redis.zadd(job_board_key, @count, @sender)
35
+ redis.rpush(sender_key, Oj.dump(work_order))
36
+ redis.publish(@channel, WorkerRoulette::JOB_NOTIFICATIONS)
31
37
  end
32
38
  end
33
39
  end
@@ -1,18 +1,24 @@
1
1
  module WorkerRoulette
2
2
  class Tradesman
3
3
  attr_reader :sender
4
- def initialize(client_pool, pubsub_pool)
4
+ def initialize(client_pool, pubsub_pool, namespace = nil)
5
5
  @client_pool = client_pool
6
6
  @pubsub_pool = pubsub_pool
7
+ @namespace = namespace
8
+ @channel = namespace || WorkerRoulette::JOB_NOTIFICATIONS
7
9
  end
8
10
 
9
11
  def job_board_key
10
- WorkerRoulette::JOB_BOARD
12
+ @job_board_key ||= "#{@namespace + ':' if @namespace}#{WorkerRoulette::JOB_BOARD}"
13
+ end
14
+
15
+ def sender_key
16
+ @sender_key ||= "#{@namespace + ':' if @namespace}#{@sender}"
11
17
  end
12
18
 
13
19
  def wait_for_work_orders(on_subscribe_callback = nil, &block)
14
20
  @pubsub_pool.with({}) do |redis|
15
- redis.subscribe(WorkerRoulette::JOB_NOTIFICATIONS) do |on|
21
+ redis.subscribe(@channel) do |on|
16
22
  on.subscribe {on_subscribe_callback.call if on_subscribe_callback}
17
23
  on.message {block.call(work_orders!) if block}
18
24
  end
@@ -23,21 +29,21 @@ module WorkerRoulette
23
29
  @client_pool.with({}) do |redis|
24
30
  get_sender_for_next_job(redis)
25
31
  results = redis.multi do
26
- redis.lrange(sender, 0, -1)
27
- redis.del(sender)
28
- redis.zrem(WorkerRoulette::JOB_BOARD, sender)
32
+ redis.lrange(sender_key, 0, -1)
33
+ redis.del(sender_key)
34
+ redis.zrem(job_board_key, sender_key)
29
35
  end
30
36
  ((results || []).first || []).map {|work_order| Oj.load(work_order)}
31
37
  end
32
38
  end
33
39
 
34
40
  def unsubscribe
35
- @pubsub_pool.with({}) {|redis| redis.unsubscribe}
41
+ @pubsub_pool.with({}) {|redis| redis.unsubscribe(@channel)}
36
42
  end
37
43
 
38
44
  private
39
45
  def get_sender_for_next_job(redis)
40
- @sender = (redis.zrange(WorkerRoulette::JOB_BOARD, 0, 0) || []).first.to_s
46
+ @sender = (redis.zrange(job_board_key, 0, 0) || []).first.to_s
41
47
  end
42
48
  end
43
49
  end
@@ -1,3 +1,3 @@
1
1
  module WorkerRoulette
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -22,14 +22,14 @@ module WorkerRoulette
22
22
  @pubsub_connection_pool = connection_pool.new(@pool_config) {Redis.new(@redis_config)}
23
23
  end
24
24
 
25
- def self.foreman(sender)
25
+ def self.foreman(sender, channel = nil)
26
26
  raise "WorkerRoulette not Started" unless @foreman_connection_pool
27
- Foreman.new(sender, @foreman_connection_pool)
27
+ Foreman.new(sender, @foreman_connection_pool, channel)
28
28
  end
29
29
 
30
- def self.tradesman
30
+ def self.tradesman(channel = nil)
31
31
  raise "WorkerRoulette not Started" unless @tradesman_connection_pool
32
- Tradesman.new(@tradesman_connection_pool, @pubsub_connection_pool)
32
+ Tradesman.new(@tradesman_connection_pool, @pubsub_connection_pool, channel)
33
33
  end
34
34
 
35
35
  def self.tradesman_connection_pool
@@ -137,15 +137,36 @@ describe WorkerRoulette do
137
137
  subject.work_orders!
138
138
  end
139
139
 
140
- it "should get the work_orders from the next sender's slot when a new job is ready" do
140
+ it "should get the work_orders from the next queue when a new job is ready" do
141
141
  subject.work_orders!
142
142
  subject.should_receive(:work_orders!).and_call_original
143
- publisher = -> {foreman.enqueue_work_order(work_orders); subject.unsubscribe; }
143
+
144
+ publisher = -> {puts :HOO.to_s; foreman.enqueue_work_order(work_orders); subject.unsubscribe}
145
+
144
146
  subject.wait_for_work_orders(publisher) do |redis_work_orders|
145
147
  redis_work_orders.should == [work_orders_with_headers]
146
148
  end
147
149
  end
148
150
 
151
+ it "should publish and subscribe on custom channels" do
152
+ tradesman = WorkerRoulette.tradesman('good_channel')
153
+ tradesman.should_receive(:work_orders!).and_call_original
154
+
155
+ good_foreman = WorkerRoulette.foreman('foreman', 'good_channel')
156
+ bad_foreman = WorkerRoulette.foreman('foreman', 'bad_channel')
157
+
158
+
159
+ publish = -> do
160
+ good_foreman.enqueue_work_order('some old fashion work')
161
+ bad_foreman.enqueue_work_order('evil biddings you should not carry out')
162
+ tradesman.unsubscribe
163
+ end
164
+
165
+ tradesman.wait_for_work_orders(publish) do |work|
166
+ work.to_s.should match("some old fashion work")
167
+ end
168
+ end
169
+
149
170
  it "should checkout a readlock for a queue and put it back when its done processing; lock should expire after 5 minutes?"
150
171
  it "should eves drop on the job board"
151
172
 
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.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: