fairway 0.0.4 → 0.0.5
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/Gemfile.lock +1 -1
- data/README.markdown +138 -78
- data/lib/fairway/config.rb +8 -15
- data/lib/fairway/connection.rb +10 -2
- data/lib/fairway/{queue_reader.rb → queue.rb} +11 -2
- data/lib/fairway/scripts.rb +17 -2
- data/lib/fairway/sidekiq/{non_blocking_fetch.rb → basic_fetch.rb} +5 -2
- data/lib/fairway/sidekiq/{queue_fetch.rb → fairway_fetch.rb} +14 -7
- data/lib/fairway/sidekiq/fetch.rb +59 -0
- data/lib/fairway/sidekiq.rb +10 -16
- data/lib/fairway/version.rb +1 -1
- data/lib/fairway.rb +7 -2
- data/spec/lib/fairway/connection_spec.rb +10 -0
- data/spec/lib/fairway/fetch_spec.rb +96 -0
- data/spec/lib/fairway/{queue_reader_spec.rb → queue_spec.rb} +46 -31
- data/spec/lib/fairway/scripts_spec.rb +21 -2
- data/spec/lib/fairway/sidekiq/{non_blocking_fetch_spec.rb → basic_fetch_spec.rb} +12 -2
- data/spec/lib/fairway/sidekiq/{queue_fetch_spec.rb → fairway_fetch_spec.rb} +19 -8
- data/spec/spec_helper.rb +1 -1
- metadata +14 -19
- data/lib/fairway/sidekiq/composite_fetch.rb +0 -37
- data/lib/fairway/sidekiq/fetcher.rb +0 -37
- data/lib/fairway/sidekiq/fetcher_factory.rb +0 -23
- data/redis/fairway_register_queue.lua +0 -5
- data/spec/lib/fairway/sidekiq/composite_fetch_spec.rb +0 -50
- data/spec/lib/fairway/sidekiq/fetcher_spec.rb +0 -31
data/Gemfile.lock
CHANGED
data/README.markdown
CHANGED
@@ -1,123 +1,183 @@
|
|
1
|
-
# Fairway
|
1
|
+
# Fairway - a fair way to queue messages in multi-user systems.
|
2
2
|
|
3
|
-
##
|
3
|
+
## Installation
|
4
4
|
|
5
|
-
|
6
|
-
* Publish messages for services who are listening
|
7
|
-
* adds messages to queues which services have registered
|
8
|
-
* keeps track of which facets have messages queued
|
9
|
-
* allows messages to be pulled off of queues in round-robin by facet
|
5
|
+
Install the gem:
|
10
6
|
|
11
|
-
|
7
|
+
gem install fairway
|
12
8
|
|
13
|
-
|
14
|
-
added to the queue, the backbone will begin pushing matching messages onto the queue.
|
9
|
+
Then make sure you `bundle install`.
|
15
10
|
|
16
|
-
|
17
|
-
queues up a ton of messages, messages for other users may be delayed.
|
11
|
+
## Configuration
|
18
12
|
|
19
|
-
|
20
|
-
|
13
|
+
Fairway.configure do |config|
|
14
|
+
config.redis = { host: "localhost", port: 6379 }
|
15
|
+
config.namespace = "fairway"
|
16
|
+
|
17
|
+
config.facet do |message|
|
18
|
+
message[:user]
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
config.register_queue("all_messages")
|
22
|
+
end
|
23
|
+
|
24
|
+
## What's a facet?
|
26
25
|
|
27
|
-
|
26
|
+
In many queuing systems, if a single user manages to queue up a lot of messages/jobs at once,
|
27
|
+
everyone else in the system has to wait. Facets are a way of splitting up a single queue by
|
28
|
+
user (or any other criteria) to ensure fair processing of each facet's jobs.
|
28
29
|
|
29
|
-
|
30
|
+
When pulling off a faceted queue, facets are processed in a round-robin fashion, so you'll pull
|
31
|
+
off one message for each facet which contains messages before doubling back and pulling
|
32
|
+
additional messages from a given facet.
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
You can define how to facet your messages during configuration:
|
35
|
+
|
36
|
+
Fairway.configure do |config|
|
37
|
+
config.facet do |message|
|
38
|
+
message[:user]
|
39
|
+
end
|
40
|
+
end
|
37
41
|
|
38
|
-
|
42
|
+
Now, any message delivered by fairway, will use the `user` key of the message to determine
|
43
|
+
which facet to use.
|
39
44
|
|
40
|
-
|
45
|
+
You could also just have a queue for each user, but at scale, this can get crazy and many
|
46
|
+
queuing systems don't perform well with thousands of queues.
|
41
47
|
|
42
|
-
|
48
|
+
## Queuing messages
|
43
49
|
|
44
|
-
|
50
|
+
In order to queue messages, you need to register a queue. You can register multiple queues,
|
51
|
+
and each queue will receive delivered messages.
|
45
52
|
|
46
|
-
|
53
|
+
Registering a queue is part of your fairway configuration:
|
47
54
|
|
48
|
-
|
55
|
+
Fairway.configure do |config|
|
56
|
+
config.register_queue("myqueue")
|
57
|
+
config.register_queue("yourqueue")
|
58
|
+
end
|
49
59
|
|
50
|
-
|
51
|
-
|
52
|
-
config.namespace = "letsdrive"
|
60
|
+
After configuring your queues, just create a fairway connection,
|
61
|
+
and it'll handle persisting your queues in Redis:
|
53
62
|
|
54
|
-
|
55
|
-
message[:user_id]
|
56
|
-
end
|
63
|
+
connection = Fairway::Connection.new
|
57
64
|
|
58
|
-
|
59
|
-
"#{message[:user_id]}:#{message[:type]}"
|
60
|
-
end
|
61
|
-
end
|
65
|
+
## Delivering messages
|
62
66
|
|
63
|
-
|
67
|
+
To add messages to your queues, you deliver them:
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
config.namespace = nil
|
69
|
+
connection = Fairway::Connection.new
|
70
|
+
connection.deliver(type: "invite_friends", user: "bob", friends: ["nancy", "john"])
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
+
Now, any registered queues will receive this message, faceted if you've defined
|
73
|
+
a facet in your configuration.
|
72
74
|
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
## Consuming messages from a queue
|
76
|
+
|
77
|
+
Once you have messages on a queue, you can pull them off and process them:
|
78
|
+
|
79
|
+
connection = Fairway::Connection.new
|
80
|
+
queue = Fairway::Queue.new(connection, "myqueue")
|
81
|
+
message = queue.pull
|
82
|
+
|
83
|
+
Behind the scenes, fairway uses a round-robin strategy to ensure equal weighting of
|
84
|
+
any facets which contain messages.
|
85
|
+
|
86
|
+
If there are no messages in any facets, `queue.pull` will return `nil`.
|
87
|
+
|
88
|
+
## Channeling messages
|
89
|
+
|
90
|
+
In many cases, you don't want all messages delivered to every queue. You'd like
|
91
|
+
to filter which messages a queue receives.
|
92
|
+
|
93
|
+
You can accomplish this with message channels. By default, all messages use the `default`
|
94
|
+
channel. You can customize this by creating a `Fairway::ChanneledConnection` and
|
95
|
+
a block which defines the channel for a given message:
|
96
|
+
|
97
|
+
conn = Fairway::Connection.new
|
98
|
+
conn = Fairway::ChanneledConnection.new(conn) do |message|
|
99
|
+
message[:type]
|
76
100
|
end
|
77
101
|
|
78
|
-
|
102
|
+
You can also register queues for a channel:
|
79
103
|
|
80
|
-
|
104
|
+
Fairway.configure do |config|
|
105
|
+
config.register_queue("invite_queue", "invite_friends")
|
106
|
+
end
|
107
|
+
|
108
|
+
Now, your queue will only receive messages which have the channel `invite_friends`.
|
81
109
|
|
82
|
-
|
110
|
+
If you'd like to receive messages with channels that match a pattern:
|
83
111
|
|
84
|
-
|
112
|
+
Fairway.configure do |config|
|
113
|
+
config.register_queue("invite_queue", "invite_.*")
|
114
|
+
end
|
85
115
|
|
86
|
-
|
87
|
-
|
116
|
+
Now, messages from the channels `invite_friends`, `invite_pets`, `invite_parents` will
|
117
|
+
be delivered to the `invite_queue`.
|
88
118
|
|
89
|
-
|
119
|
+
## Subscribing to messages
|
90
120
|
|
91
|
-
|
121
|
+
To listen for messages without the overhead of queuing them, you can subscribe:
|
92
122
|
|
93
|
-
|
123
|
+
connection = Fairway::Connection.new
|
94
124
|
|
95
|
-
|
96
|
-
|
97
|
-
puts "[#{channel}] #{message}"
|
98
|
-
end
|
125
|
+
connection.subscribe do |message|
|
126
|
+
# Do something with each message
|
99
127
|
end
|
100
128
|
|
101
|
-
If you'
|
129
|
+
If you'd like to only receive some messages, you can subscribe to just a particular channel:
|
102
130
|
|
103
|
-
|
131
|
+
connection = Fairway::Connection.new
|
104
132
|
|
105
|
-
*
|
106
|
-
|
133
|
+
connection.subscribe("invite_*") do |message|
|
134
|
+
# Do something with each message which
|
135
|
+
# has a channel matching "invite_*"
|
136
|
+
end
|
107
137
|
|
108
|
-
|
138
|
+
## Fairway and Sidekiq
|
109
139
|
|
110
|
-
|
140
|
+
Fairway isn't meant to be a robust system for processing queued messages/jobs. To more reliably
|
141
|
+
process queued messages, we've integrated with [Sidekiq](http://sidekiq.org/).
|
111
142
|
|
112
|
-
|
143
|
+
require 'fairway/sidekiq'
|
113
144
|
|
114
|
-
|
145
|
+
connection = Fairway::Connection.new
|
146
|
+
queues = Fairway::Queue.new(connection, "myqueue", "yourqueue")
|
115
147
|
|
116
|
-
|
117
|
-
from
|
148
|
+
Sidekiq.options[:fetch] = Fairway::Sidekiq::Fetch.new do |fetch|
|
149
|
+
fetch.from :sidekiq, 2
|
150
|
+
fetch.from queues, 1 do |queue, message|
|
151
|
+
# translate message to normalized Sidekiq job, if needed
|
152
|
+
{ "queue" => "fairway",
|
153
|
+
"class" => "FairwayMessageJob",
|
154
|
+
"args" => [message],
|
155
|
+
"retry" => true }
|
156
|
+
end
|
157
|
+
end
|
118
158
|
|
119
|
-
|
159
|
+
`fetch.from :sidekiq, 2` will fetch from sidekiq queues you have defined through the
|
160
|
+
normal sidekiq configuration.
|
120
161
|
|
121
|
-
|
162
|
+
`fetch.from queues, 1` will pull messages from your fairway queue, and allow you to translate
|
163
|
+
them into standard sidekiq jobs.
|
164
|
+
|
165
|
+
The second parameters are fetch weights, so in the above example, we'll look for jobs first from
|
166
|
+
your normal sidekiq queues twice as often as your fairway queues.
|
167
|
+
|
168
|
+
## Queue structure
|
169
|
+
|
170
|
+
TODO: low level description of what's going on? performance?
|
171
|
+
|
172
|
+
## LUA scripting
|
173
|
+
|
174
|
+
Fairway uses [LUA scripting](http://redis.io/commands/eval) heavily. This is for a few reasons:
|
175
|
+
|
176
|
+
* There is complex logic that can't be expressed in normal redis commands.
|
177
|
+
* Each script contains many redis commands and it's important that these
|
178
|
+
commands are processed atomically.
|
179
|
+
* Since the script is run inside of redis, once the script has started,
|
180
|
+
there's very low latency for each redis command. So, the script executes
|
181
|
+
much faster than if we made each call independantly over the network.
|
122
182
|
|
123
|
-
|
183
|
+
This means your must be using a Redis version `>= 2.6.0`
|
data/lib/fairway/config.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module Fairway
|
2
2
|
class Config
|
3
3
|
attr_accessor :namespace
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :defined_queues
|
5
5
|
|
6
6
|
DEFAULT_FACET = "default"
|
7
7
|
|
8
8
|
QueueDefinition = Struct.new(:name, :channel)
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@redis_options
|
12
|
-
@namespace
|
13
|
-
@facet
|
14
|
-
@
|
11
|
+
@redis_options = {}
|
12
|
+
@namespace = nil
|
13
|
+
@facet = lambda { |message| DEFAULT_FACET }
|
14
|
+
@defined_queues = []
|
15
|
+
|
15
16
|
yield self if block_given?
|
16
17
|
end
|
17
18
|
|
@@ -24,7 +25,7 @@ module Fairway
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def register_queue(name, channel = Connection::DEFAULT_CHANNEL)
|
27
|
-
@
|
28
|
+
@defined_queues << QueueDefinition.new(name, channel)
|
28
29
|
end
|
29
30
|
|
30
31
|
def redis=(options)
|
@@ -36,19 +37,11 @@ module Fairway
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def scripts
|
39
|
-
@scripts ||= Scripts.new(raw_redis,
|
40
|
+
@scripts ||= Scripts.new(raw_redis, @namespace)
|
40
41
|
end
|
41
42
|
|
42
43
|
private
|
43
44
|
|
44
|
-
def scripts_namespace
|
45
|
-
if @namespace.blank?
|
46
|
-
""
|
47
|
-
else
|
48
|
-
"#{@namespace}:"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
45
|
def raw_redis
|
53
46
|
@raw_redis ||= Redis.new(@redis_options.merge(hiredis: true))
|
54
47
|
end
|
data/lib/fairway/connection.rb
CHANGED
@@ -7,8 +7,16 @@ module Fairway
|
|
7
7
|
def initialize(config = Fairway.config)
|
8
8
|
@config = config
|
9
9
|
|
10
|
-
@config.
|
11
|
-
scripts.
|
10
|
+
@config.defined_queues.each do |queue|
|
11
|
+
scripts.register_queue(queue.name, queue.channel)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def queues
|
16
|
+
@queues ||= begin
|
17
|
+
scripts.registered_queues.map do |name, _|
|
18
|
+
Queue.new(self, name)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
end
|
14
22
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module Fairway
|
2
|
-
class
|
2
|
+
class Queue
|
3
|
+
attr_reader :connection, :queue_names
|
4
|
+
|
3
5
|
def initialize(connection, *queue_names)
|
4
|
-
@connection
|
6
|
+
@connection = connection
|
5
7
|
@queue_names = [queue_names].flatten!
|
6
8
|
end
|
7
9
|
|
@@ -12,5 +14,12 @@ module Fairway
|
|
12
14
|
def pull
|
13
15
|
@connection.scripts.fairway_pull(@queue_names)
|
14
16
|
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
other.respond_to?(:connection) &&
|
20
|
+
other.respond_to?(:queue_names) &&
|
21
|
+
connection == other.connection &&
|
22
|
+
queue_names == other.queue_names
|
23
|
+
end
|
15
24
|
end
|
16
25
|
end
|
data/lib/fairway/scripts.rb
CHANGED
@@ -12,9 +12,17 @@ module Fairway
|
|
12
12
|
@namespace = namespace
|
13
13
|
end
|
14
14
|
|
15
|
+
def register_queue(name, channel)
|
16
|
+
@redis.hset(registered_queues_key, name, channel)
|
17
|
+
end
|
18
|
+
|
19
|
+
def registered_queues
|
20
|
+
@redis.hgetall(registered_queues_key)
|
21
|
+
end
|
22
|
+
|
15
23
|
def method_missing(method_name, *args)
|
16
24
|
loaded = false
|
17
|
-
@redis.evalsha(script_sha(method_name), [
|
25
|
+
@redis.evalsha(script_sha(method_name), [namespace], args)
|
18
26
|
rescue Redis::CommandError => ex
|
19
27
|
if ex.message.include?("NOSCRIPT") && !loaded
|
20
28
|
@redis.script(:load, script_source(method_name))
|
@@ -27,6 +35,14 @@ module Fairway
|
|
27
35
|
|
28
36
|
private
|
29
37
|
|
38
|
+
def registered_queues_key
|
39
|
+
"#{namespace}registered_queues"
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace
|
43
|
+
@namespace.blank? ? "" : "#{@namespace}:"
|
44
|
+
end
|
45
|
+
|
30
46
|
def script_sha(name)
|
31
47
|
self.class.script_shas[name] ||= Digest::SHA1.hexdigest(script_source(name))
|
32
48
|
end
|
@@ -38,6 +54,5 @@ module Fairway
|
|
38
54
|
def script_path(name)
|
39
55
|
Pathname.new(__FILE__).dirname.join("../../redis/#{name}.lua")
|
40
56
|
end
|
41
|
-
|
42
57
|
end
|
43
58
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Fairway
|
2
2
|
module Sidekiq
|
3
|
-
class
|
3
|
+
class BasicFetch < ::Sidekiq::BasicFetch
|
4
4
|
attr_reader :queues
|
5
5
|
|
6
6
|
def initialize(options)
|
7
7
|
@queues = options[:queues].map { |q| "queue:#{q}" }
|
8
8
|
end
|
9
9
|
|
10
|
-
def retrieve_work
|
10
|
+
def retrieve_work(options = {})
|
11
|
+
options = { blocking: true }.merge(options)
|
12
|
+
|
11
13
|
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work #{queues_cmd}"
|
12
14
|
|
13
15
|
work = ::Sidekiq.redis do |conn|
|
@@ -32,6 +34,7 @@ module Fairway
|
|
32
34
|
work = UnitOfWork.new(*work)
|
33
35
|
else
|
34
36
|
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got nil"
|
37
|
+
sleep 1 if options[:blocking]
|
35
38
|
end
|
36
39
|
|
37
40
|
work
|
@@ -1,18 +1,20 @@
|
|
1
|
-
require "sidekiq/fetch"
|
2
|
-
|
3
1
|
module Fairway
|
4
2
|
module Sidekiq
|
5
|
-
class
|
6
|
-
|
7
|
-
|
3
|
+
class FairwayFetch < ::Sidekiq::BasicFetch
|
4
|
+
attr_reader :queues
|
5
|
+
|
6
|
+
def initialize(queues, &block)
|
7
|
+
@queues = queues
|
8
8
|
@message_to_job = block if block_given?
|
9
9
|
end
|
10
10
|
|
11
|
-
def retrieve_work
|
11
|
+
def retrieve_work(options = {})
|
12
|
+
options = { blocking: true }.merge(options)
|
13
|
+
|
12
14
|
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work"
|
13
15
|
unit_of_work = nil
|
14
16
|
|
15
|
-
fairway_queue, work = @
|
17
|
+
fairway_queue, work = @queues.pull
|
16
18
|
|
17
19
|
if work
|
18
20
|
decoded_work = JSON.parse(work)
|
@@ -29,10 +31,15 @@ module Fairway
|
|
29
31
|
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got work"
|
30
32
|
else
|
31
33
|
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got nil"
|
34
|
+
sleep 1 if options[:blocking]
|
32
35
|
end
|
33
36
|
|
34
37
|
unit_of_work
|
35
38
|
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
other.respond_to?(:queues) && queues == other.queues
|
42
|
+
end
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Fairway
|
2
|
+
module Sidekiq
|
3
|
+
class Fetch
|
4
|
+
class Fetches
|
5
|
+
attr_reader :list
|
6
|
+
|
7
|
+
def from(queue, weight = 1, &block)
|
8
|
+
if queue == :sidekiq
|
9
|
+
queue = BasicFetch.new(::Sidekiq.options)
|
10
|
+
else
|
11
|
+
queue = FairwayFetch.new(queue, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
weight.times do
|
15
|
+
list << queue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def list
|
20
|
+
@list ||= []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(&block)
|
25
|
+
yield(@fetches = Fetches.new)
|
26
|
+
end
|
27
|
+
|
28
|
+
def new(options)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetches
|
33
|
+
@fetches.list
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch_order
|
37
|
+
fetches.shuffle.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def retrieve_work
|
41
|
+
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work"
|
42
|
+
|
43
|
+
fetch_order.each do |fetch|
|
44
|
+
work = fetch.retrieve_work(blocking: false)
|
45
|
+
|
46
|
+
if work
|
47
|
+
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got work"
|
48
|
+
return work
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got nil"
|
53
|
+
sleep 1
|
54
|
+
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/fairway/sidekiq.rb
CHANGED
@@ -1,20 +1,14 @@
|
|
1
1
|
require "sidekiq"
|
2
|
-
require "sidekiq/
|
2
|
+
require "sidekiq/fetch"
|
3
3
|
|
4
|
-
require "fairway/sidekiq/
|
5
|
-
require "fairway/sidekiq/
|
6
|
-
require "fairway/sidekiq/
|
7
|
-
require "fairway/sidekiq/non_blocking_fetch"
|
8
|
-
require "fairway/sidekiq/queue_fetch"
|
4
|
+
require "fairway/sidekiq/fetch"
|
5
|
+
require "fairway/sidekiq/basic_fetch"
|
6
|
+
require "fairway/sidekiq/fairway_fetch"
|
9
7
|
|
10
|
-
# conn
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# queue_fetch = Fairway::Sidekiq::QueueFetch.new(queue_reader) do |message|
|
14
|
-
# # Transform message into a sidekiq job
|
15
|
-
# message
|
16
|
-
# end
|
8
|
+
# conn = Fairway::Connection.new
|
9
|
+
# queues = Fairway::Queue.new(conn, "queue1", "queue2")
|
17
10
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
11
|
+
# Sidekiq.options[:fetch] = Fairway::Sidekiq::Fetch.new do |fetch|
|
12
|
+
# fetch.from :sidekiq, 1
|
13
|
+
# fetch.from queues, 1
|
14
|
+
# end
|
data/lib/fairway/version.rb
CHANGED
data/lib/fairway.rb
CHANGED
@@ -9,7 +9,7 @@ require "fairway/config"
|
|
9
9
|
require "fairway/scripts"
|
10
10
|
require "fairway/channeled_connection"
|
11
11
|
require "fairway/connection"
|
12
|
-
require "fairway/
|
12
|
+
require "fairway/queue"
|
13
13
|
|
14
14
|
module Fairway
|
15
15
|
def self.config
|
@@ -17,6 +17,11 @@ module Fairway
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.configure
|
20
|
-
yield(config)
|
20
|
+
yield(config)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.reconfigure
|
24
|
+
@config = Config.new
|
25
|
+
yield(config)
|
21
26
|
end
|
22
27
|
end
|
@@ -23,6 +23,16 @@ module Fairway
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe "#queues" do
|
27
|
+
it "returns a Queue for every currently registered queue" do
|
28
|
+
Fairway.config.redis.hset("registered_queues", "name", "channel")
|
29
|
+
|
30
|
+
connection.queues.should == [
|
31
|
+
Queue.new(connection, "name")
|
32
|
+
]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
26
36
|
describe "#deliver" do
|
27
37
|
it "publishes message over the message topic channel" do
|
28
38
|
redis = Redis.new
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Fairway::Sidekiq
|
4
|
+
describe Fetch do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "accepts a block to define of fetches with priority" do
|
7
|
+
fetch = Fetch.new do |fetch|
|
8
|
+
fetch.from :fetchA, 10
|
9
|
+
fetch.from :fetchB, 1
|
10
|
+
end
|
11
|
+
|
12
|
+
fetchA = FairwayFetch.new(:fetchA)
|
13
|
+
fetchB = FairwayFetch.new(:fetchB)
|
14
|
+
|
15
|
+
fetch.fetches.should == [Array.new(10, fetchA), fetchB].flatten
|
16
|
+
end
|
17
|
+
|
18
|
+
it "instantiates a BasicFetch if you fetch from the keyword :sidekiq" do
|
19
|
+
fetch = Fetch.new do |fetch|
|
20
|
+
fetch.from :sidekiq, 1
|
21
|
+
end
|
22
|
+
|
23
|
+
fetch.fetches.length.should == 1
|
24
|
+
fetch.fetches.first.should be_instance_of(BasicFetch)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "instantiates a FairwayFetch if you fetch from a queue object" do
|
28
|
+
queue = Fairway::Queue.new(Fairway::Connection.new, "fairway")
|
29
|
+
|
30
|
+
fetch = Fetch.new do |fetch|
|
31
|
+
fetch.from queue, 1
|
32
|
+
end
|
33
|
+
|
34
|
+
fetch.fetches.length.should == 1
|
35
|
+
fetch.fetches.first.should be_instance_of(FairwayFetch)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#new" do
|
40
|
+
it "returns itself to match Sidekiq fetch API" do
|
41
|
+
fetch = Fetch.new do |fetch|
|
42
|
+
fetch.from :fetchA, 1
|
43
|
+
end
|
44
|
+
|
45
|
+
fetch.new({}).should == fetch
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#fetch_order" do
|
50
|
+
let(:fetch) { Fetch.new { |f| f.from :fetchA, 10; f.from :fetchB, 1 } }
|
51
|
+
|
52
|
+
it "should shuffle and uniq fetches" do
|
53
|
+
fetch.fetches.should_receive(:shuffle).and_return(fetch.fetches)
|
54
|
+
fetch.fetch_order
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should unique fetches list" do
|
58
|
+
fetch.fetches.length.should == 11
|
59
|
+
fetch.fetch_order.length.should == 2
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#retrieve_work" do
|
64
|
+
let(:work) { mock(:work) }
|
65
|
+
let(:fetchA) { mock(:fetch) }
|
66
|
+
let(:fetchB) { mock(:fetch) }
|
67
|
+
let(:fetch) { Fetch.new { |f| f.from :fetchA, 10; f.from :fetchB, 1 } }
|
68
|
+
|
69
|
+
before do
|
70
|
+
fetch.stub(fetch_order: [fetchA, fetchB], sleep: nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns work from the first fetch who has work" do
|
74
|
+
fetchA.stub(retrieve_work: work)
|
75
|
+
fetchB.should_not_receive(:retrieve_work)
|
76
|
+
|
77
|
+
fetch.retrieve_work.should == work
|
78
|
+
end
|
79
|
+
|
80
|
+
it "attempts to retrieve work from each fetch in a non blocking fashion" do
|
81
|
+
fetchA.should_receive(:retrieve_work).with(blocking: false)
|
82
|
+
fetchB.should_receive(:retrieve_work).with(blocking: false)
|
83
|
+
fetch.retrieve_work.should be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it "sleeps if no work is found" do
|
87
|
+
fetch.should_receive(:sleep).with(1)
|
88
|
+
|
89
|
+
fetchA.stub(retrieve_work: nil)
|
90
|
+
fetchB.stub(retrieve_work: nil)
|
91
|
+
|
92
|
+
fetch.retrieve_work
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
module Fairway
|
4
|
-
describe
|
4
|
+
describe Queue do
|
5
5
|
let(:connection) do
|
6
6
|
c = Connection.new(Fairway.config)
|
7
7
|
ChanneledConnection.new(c) do |message|
|
@@ -12,31 +12,31 @@ module Fairway
|
|
12
12
|
|
13
13
|
describe "#initialize" do
|
14
14
|
it "requires a Connection and queue names" do
|
15
|
-
lambda {
|
15
|
+
lambda { Queue.new }.should raise_error(ArgumentError)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
describe "#length" do
|
20
|
-
let(:
|
20
|
+
let(:queue) { Queue.new(connection, "myqueue") }
|
21
21
|
|
22
22
|
before do
|
23
23
|
Fairway.config.register_queue("myqueue", "event:helloworld")
|
24
24
|
end
|
25
25
|
|
26
26
|
it "returns the number of queued messages across facets" do
|
27
|
-
|
27
|
+
queue.length.should == 0
|
28
28
|
|
29
29
|
connection.deliver(message.merge(facet: 1, message: 1))
|
30
30
|
connection.deliver(message.merge(facet: 1, message: 2))
|
31
31
|
connection.deliver(message.merge(facet: 2, message: 3))
|
32
32
|
|
33
|
-
|
33
|
+
queue.length.should == 3
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
queue.pull
|
36
|
+
queue.pull
|
37
|
+
queue.pull
|
38
38
|
|
39
|
-
|
39
|
+
queue.length.should == 0
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -49,9 +49,9 @@ module Fairway
|
|
49
49
|
connection.deliver(message1 = message.merge(message: 1))
|
50
50
|
connection.deliver(message2 = message.merge(message: 2))
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
queue = Queue.new(connection, "myqueue")
|
53
|
+
queue.pull.should == ["myqueue", message1.to_json]
|
54
|
+
queue.pull.should == ["myqueue", message2.to_json]
|
55
55
|
end
|
56
56
|
|
57
57
|
it "pulls from facets of the queue in a round-robin nature" do
|
@@ -59,27 +59,27 @@ module Fairway
|
|
59
59
|
connection.deliver(message2 = message.merge(facet: 1, message: 2))
|
60
60
|
connection.deliver(message3 = message.merge(facet: 2, message: 3))
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
queue = Queue.new(connection, "myqueue")
|
63
|
+
queue.pull.should == ["myqueue", message1.to_json]
|
64
|
+
queue.pull.should == ["myqueue", message3.to_json]
|
65
|
+
queue.pull.should == ["myqueue", message2.to_json]
|
66
66
|
end
|
67
67
|
|
68
68
|
it "removes facet from active list if it becomes empty" do
|
69
69
|
connection.deliver(message)
|
70
70
|
|
71
71
|
Fairway.config.redis.smembers("myqueue:active_facets").should == ["1"]
|
72
|
-
|
73
|
-
|
72
|
+
queue = Queue.new(connection, "myqueue")
|
73
|
+
queue.pull
|
74
74
|
Fairway.config.redis.smembers("myqueue:active_facets").should be_empty
|
75
75
|
end
|
76
76
|
|
77
77
|
it "returns nil if there are no messages to retrieve" do
|
78
78
|
connection.deliver(message)
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
queue = Queue.new(connection, "myqueue")
|
81
|
+
queue.pull.should == ["myqueue", message.to_json]
|
82
|
+
queue.pull.should be_nil
|
83
83
|
end
|
84
84
|
|
85
85
|
context "pulling from multiple queues" do
|
@@ -92,14 +92,14 @@ module Fairway
|
|
92
92
|
connection.deliver(message1 = message.merge(topic: "event:1"))
|
93
93
|
connection.deliver(message2 = message.merge(topic: "event:2"))
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
queue = Queue.new(connection, "myqueue2", "myqueue1")
|
96
|
+
queue.pull.should == ["myqueue2", message2.to_json]
|
97
|
+
queue.pull.should == ["myqueue1", message1.to_json]
|
98
98
|
end
|
99
99
|
|
100
100
|
it "returns nil if no queues have messages" do
|
101
|
-
|
102
|
-
|
101
|
+
queue = Queue.new(connection, "myqueue2", "myqueue1")
|
102
|
+
queue.pull.should be_nil
|
103
103
|
end
|
104
104
|
|
105
105
|
it "pulls from facets of the queue in a round-robin nature" do
|
@@ -108,13 +108,28 @@ module Fairway
|
|
108
108
|
connection.deliver(message3 = message.merge(facet: 2, topic: "event:1"))
|
109
109
|
connection.deliver(message4 = message.merge(facet: 1, topic: "event:2"))
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
queue = Queue.new(connection, "myqueue2", "myqueue1")
|
112
|
+
queue.pull.should == ["myqueue2", message4.to_json]
|
113
|
+
queue.pull.should == ["myqueue1", message1.to_json]
|
114
|
+
queue.pull.should == ["myqueue1", message3.to_json]
|
115
|
+
queue.pull.should == ["myqueue1", message2.to_json]
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
119
|
+
|
120
|
+
describe "equality" do
|
121
|
+
it "should equal queues with same connection and queue names" do
|
122
|
+
Queue.new(connection, "a", "b", "c").should == Queue.new(connection, "a", "b", "c")
|
123
|
+
end
|
124
|
+
|
125
|
+
it "doesn't equal queues with different connection" do
|
126
|
+
new_conn = Connection.new(Fairway.config)
|
127
|
+
Queue.new(connection, "a", "b", "c").should_not == Queue.new(new_conn, "a", "b", "c")
|
128
|
+
end
|
129
|
+
|
130
|
+
it "doesn't equal queues with different queues" do
|
131
|
+
Queue.new(connection, "a", "b", "c").should_not == Queue.new(connection, "a", "b")
|
132
|
+
end
|
133
|
+
end
|
119
134
|
end
|
120
135
|
end
|
@@ -10,17 +10,36 @@ module Fairway
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
describe "#register_queue" do
|
14
|
+
let(:scripts) { Scripts.new(Redis.new, "foo") }
|
15
|
+
|
16
|
+
it "adds the queue and channel to the hash of registered queues" do
|
17
|
+
scripts.register_queue("name", "channel")
|
18
|
+
Redis.new.hgetall("foo:registered_queues").should == { "name" => "channel" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#registered_queue" do
|
23
|
+
let(:scripts) { Scripts.new(Redis.new, "foo") }
|
24
|
+
|
25
|
+
it "returns hash of all registered queues and their channels" do
|
26
|
+
Redis.new.hset("foo:registered_queues", "first", "channel1")
|
27
|
+
Redis.new.hset("foo:registered_queues", "second", "channel2")
|
28
|
+
scripts.registered_queues.should == { "first" => "channel1", "second" => "channel2" }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
13
32
|
describe "#method_missing" do
|
14
33
|
let(:scripts) { Scripts.new(Redis.new, "foo") }
|
15
34
|
|
16
35
|
it "runs the script" do
|
17
|
-
scripts.
|
36
|
+
scripts.fairway_pull("namespace", "name")
|
18
37
|
end
|
19
38
|
|
20
39
|
context "when the script does not exist" do
|
21
40
|
it "loads the script" do
|
22
41
|
Redis.new.script(:flush)
|
23
|
-
scripts.
|
42
|
+
scripts.fairway_pull("namespace", "name")
|
24
43
|
end
|
25
44
|
end
|
26
45
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
module Fairway::Sidekiq
|
4
|
-
describe
|
4
|
+
describe BasicFetch do
|
5
5
|
let(:queues) { [:critical, :critical, :default] }
|
6
|
-
let(:fetch) {
|
6
|
+
let(:fetch) { BasicFetch.new(queues: queues) }
|
7
7
|
|
8
8
|
it "accepts options with a list of queues and their weights" do
|
9
9
|
fetch.queues.should == ["queue:critical", "queue:critical", "queue:default"]
|
@@ -26,6 +26,16 @@ module Fairway::Sidekiq
|
|
26
26
|
unit_of_work.queue_name.should == "critical"
|
27
27
|
unit_of_work.message.should == "critical"
|
28
28
|
end
|
29
|
+
|
30
|
+
it "sleeps if no work is found" do
|
31
|
+
fetch.should_receive(:sleep).with(1)
|
32
|
+
fetch.retrieve_work
|
33
|
+
end
|
34
|
+
|
35
|
+
it "doesn't sleep if blocking option is false" do
|
36
|
+
fetch.should_not_receive(:sleep)
|
37
|
+
fetch.retrieve_work(blocking: false)
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
@@ -2,14 +2,13 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
module Fairway
|
4
4
|
module Sidekiq
|
5
|
-
describe
|
6
|
-
let(:
|
5
|
+
describe FairwayFetch do
|
6
|
+
let(:queue) { Queue.new(Connection.new, "fairway") }
|
7
7
|
let(:work) { { queue: "golf_events", type: "swing", name: "putt" }.to_json }
|
8
|
+
let(:fetch) { FairwayFetch.new(queue) }
|
8
9
|
|
9
|
-
it "requests work from the queue
|
10
|
-
|
11
|
-
|
12
|
-
reader.stub(pull: ["fairway", work])
|
10
|
+
it "requests work from the queue queue" do
|
11
|
+
queue.stub(pull: ["fairway", work])
|
13
12
|
|
14
13
|
unit_of_work = fetch.retrieve_work
|
15
14
|
unit_of_work.queue_name.should == "golf_events"
|
@@ -17,19 +16,31 @@ module Fairway
|
|
17
16
|
end
|
18
17
|
|
19
18
|
it "allows transforming of the message into a job" do
|
20
|
-
fetch =
|
19
|
+
fetch = FairwayFetch.new(queue) do |fairway_queue, message|
|
21
20
|
{
|
22
21
|
"queue" => "my_#{message["queue"]}",
|
23
22
|
"class" => "GolfEventJob"
|
24
23
|
}
|
25
24
|
end
|
26
25
|
|
27
|
-
|
26
|
+
queue.stub(pull: ["fairway", work])
|
28
27
|
|
29
28
|
unit_of_work = fetch.retrieve_work
|
30
29
|
unit_of_work.queue_name.should == "my_golf_events"
|
31
30
|
unit_of_work.message.should == { "queue" => "my_golf_events", "class" => "GolfEventJob" }.to_json
|
32
31
|
end
|
32
|
+
|
33
|
+
it "sleeps if no work is found" do
|
34
|
+
fetch.should_receive(:sleep).with(1)
|
35
|
+
queue.stub(pull: nil)
|
36
|
+
fetch.retrieve_work
|
37
|
+
end
|
38
|
+
|
39
|
+
it "doesn't sleep if blocking option is false" do
|
40
|
+
fetch.should_not_receive(:sleep)
|
41
|
+
queue.stub(pull: nil)
|
42
|
+
fetch.retrieve_work(blocking: false)
|
43
|
+
end
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -22,7 +22,7 @@ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f}
|
|
22
22
|
|
23
23
|
RSpec.configure do |config|
|
24
24
|
config.before(:each) do
|
25
|
-
Fairway.
|
25
|
+
Fairway.reconfigure do |config|
|
26
26
|
config.namespace = "test:fairway"
|
27
27
|
config.facet { |message| message[:facet] }
|
28
28
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fairway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
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: 2013-02-
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -94,27 +94,23 @@ files:
|
|
94
94
|
- lib/fairway/channeled_connection.rb
|
95
95
|
- lib/fairway/config.rb
|
96
96
|
- lib/fairway/connection.rb
|
97
|
-
- lib/fairway/
|
97
|
+
- lib/fairway/queue.rb
|
98
98
|
- lib/fairway/scripts.rb
|
99
99
|
- lib/fairway/sidekiq.rb
|
100
|
-
- lib/fairway/sidekiq/
|
101
|
-
- lib/fairway/sidekiq/
|
102
|
-
- lib/fairway/sidekiq/
|
103
|
-
- lib/fairway/sidekiq/non_blocking_fetch.rb
|
104
|
-
- lib/fairway/sidekiq/queue_fetch.rb
|
100
|
+
- lib/fairway/sidekiq/basic_fetch.rb
|
101
|
+
- lib/fairway/sidekiq/fairway_fetch.rb
|
102
|
+
- lib/fairway/sidekiq/fetch.rb
|
105
103
|
- lib/fairway/version.rb
|
106
104
|
- redis/fairway_deliver.lua
|
107
105
|
- redis/fairway_pull.lua
|
108
|
-
- redis/fairway_register_queue.lua
|
109
106
|
- spec/lib/fairway/channeled_connection_spec.rb
|
110
107
|
- spec/lib/fairway/config_spec.rb
|
111
108
|
- spec/lib/fairway/connection_spec.rb
|
112
|
-
- spec/lib/fairway/
|
109
|
+
- spec/lib/fairway/fetch_spec.rb
|
110
|
+
- spec/lib/fairway/queue_spec.rb
|
113
111
|
- spec/lib/fairway/scripts_spec.rb
|
114
|
-
- spec/lib/fairway/sidekiq/
|
115
|
-
- spec/lib/fairway/sidekiq/
|
116
|
-
- spec/lib/fairway/sidekiq/non_blocking_fetch_spec.rb
|
117
|
-
- spec/lib/fairway/sidekiq/queue_fetch_spec.rb
|
112
|
+
- spec/lib/fairway/sidekiq/basic_fetch_spec.rb
|
113
|
+
- spec/lib/fairway/sidekiq/fairway_fetch_spec.rb
|
118
114
|
- spec/lib/fairway/subscription_spec.rb
|
119
115
|
- spec/spec_helper.rb
|
120
116
|
homepage: https://github.com/customerio/fairway
|
@@ -145,11 +141,10 @@ test_files:
|
|
145
141
|
- spec/lib/fairway/channeled_connection_spec.rb
|
146
142
|
- spec/lib/fairway/config_spec.rb
|
147
143
|
- spec/lib/fairway/connection_spec.rb
|
148
|
-
- spec/lib/fairway/
|
144
|
+
- spec/lib/fairway/fetch_spec.rb
|
145
|
+
- spec/lib/fairway/queue_spec.rb
|
149
146
|
- spec/lib/fairway/scripts_spec.rb
|
150
|
-
- spec/lib/fairway/sidekiq/
|
151
|
-
- spec/lib/fairway/sidekiq/
|
152
|
-
- spec/lib/fairway/sidekiq/non_blocking_fetch_spec.rb
|
153
|
-
- spec/lib/fairway/sidekiq/queue_fetch_spec.rb
|
147
|
+
- spec/lib/fairway/sidekiq/basic_fetch_spec.rb
|
148
|
+
- spec/lib/fairway/sidekiq/fairway_fetch_spec.rb
|
154
149
|
- spec/lib/fairway/subscription_spec.rb
|
155
150
|
- spec/spec_helper.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Fairway
|
2
|
-
module Sidekiq
|
3
|
-
class CompositeFetch
|
4
|
-
attr_reader :fetches
|
5
|
-
|
6
|
-
def initialize(fetches)
|
7
|
-
@fetches = []
|
8
|
-
|
9
|
-
fetches.each do |fetch, weight|
|
10
|
-
[weight.to_i, 1].max.times do
|
11
|
-
@fetches << fetch
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def fetch_order
|
17
|
-
fetches.shuffle.uniq
|
18
|
-
end
|
19
|
-
|
20
|
-
def retrieve_work
|
21
|
-
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work"
|
22
|
-
|
23
|
-
fetch_order.each do |fetch|
|
24
|
-
work = fetch.retrieve_work
|
25
|
-
|
26
|
-
if work
|
27
|
-
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got work"
|
28
|
-
return work
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
::Sidekiq.logger.debug "#{self.class.name}#retrieve_work got nil"
|
33
|
-
return nil
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Fairway
|
2
|
-
module Sidekiq
|
3
|
-
class Fetcher < ::Sidekiq::Fetcher
|
4
|
-
attr_reader :mgr, :strategy
|
5
|
-
|
6
|
-
def initialize(mgr, fetch)
|
7
|
-
@mgr = mgr
|
8
|
-
@strategy = fetch
|
9
|
-
end
|
10
|
-
|
11
|
-
def done!
|
12
|
-
@done = true
|
13
|
-
end
|
14
|
-
|
15
|
-
def fetch
|
16
|
-
watchdog('Fetcher#fetch died') do
|
17
|
-
return if @done
|
18
|
-
|
19
|
-
begin
|
20
|
-
work = @strategy.retrieve_work
|
21
|
-
|
22
|
-
if work
|
23
|
-
@mgr.async.assign(work)
|
24
|
-
else
|
25
|
-
after(TIMEOUT) { fetch }
|
26
|
-
end
|
27
|
-
rescue => ex
|
28
|
-
logger.error("Error fetching message: #{ex}")
|
29
|
-
logger.error(ex.backtrace.first)
|
30
|
-
sleep(TIMEOUT)
|
31
|
-
after(0) { fetch }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Fairway
|
2
|
-
module Sidekiq
|
3
|
-
class FetcherFactory
|
4
|
-
def initialize(fetch)
|
5
|
-
@fetch = fetch
|
6
|
-
end
|
7
|
-
|
8
|
-
def done!
|
9
|
-
@fetcher.done!
|
10
|
-
end
|
11
|
-
|
12
|
-
def strategy
|
13
|
-
# This is only used for ::Sidekiq::BasicFetch.bulk_requeue
|
14
|
-
# which is the same for us.
|
15
|
-
::Sidekiq::BasicFetch
|
16
|
-
end
|
17
|
-
|
18
|
-
def new(mgr, options)
|
19
|
-
@fetcher = Fetcher.new(mgr, @fetch)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Fairway::Sidekiq
|
4
|
-
describe CompositeFetch do
|
5
|
-
describe "#initialize" do
|
6
|
-
it "accepts a hash of fetches with priority" do
|
7
|
-
fetcher = CompositeFetch.new(fetcherA: 10, fetcherB: 1)
|
8
|
-
fetcher.fetches.should == [Array.new(10, :fetcherA), :fetcherB].flatten
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe "#fetch_order" do
|
13
|
-
let(:fetcher) { CompositeFetch.new(fetcherA: 10, fetcherB: 1) }
|
14
|
-
|
15
|
-
it "should shuffle and uniq fetches" do
|
16
|
-
fetcher.fetches.should_receive(:shuffle).and_return(fetcher.fetches)
|
17
|
-
fetcher.fetch_order
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should unique fetches list" do
|
21
|
-
fetcher.fetches.length.should == 11
|
22
|
-
fetcher.fetch_order.length.should == 2
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "#retrieve_work" do
|
27
|
-
let(:work) { mock(:work) }
|
28
|
-
let(:fetcherA) { mock(:fetcher) }
|
29
|
-
let(:fetcherB) { mock(:fetcher) }
|
30
|
-
let(:fetcher) { CompositeFetch.new(fetcherA => 10, fetcherB => 1) }
|
31
|
-
|
32
|
-
before do
|
33
|
-
fetcher.stub(fetch_order: [fetcherA, fetcherB])
|
34
|
-
end
|
35
|
-
|
36
|
-
it "returns work from the first fetcher who has work" do
|
37
|
-
fetcherA.stub(retrieve_work: work)
|
38
|
-
fetcherB.should_not_receive(:retrieve_work)
|
39
|
-
|
40
|
-
fetcher.retrieve_work.should == work
|
41
|
-
end
|
42
|
-
|
43
|
-
it "attempts to retrieve work from each fetcher if no work is found" do
|
44
|
-
fetcherA.should_receive(:retrieve_work)
|
45
|
-
fetcherB.should_receive(:retrieve_work)
|
46
|
-
fetcher.retrieve_work.should be_nil
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Fairway::Sidekiq
|
4
|
-
describe Fetcher do
|
5
|
-
let(:manager) { mock(:manager) }
|
6
|
-
let(:fetch) { mock(:fetch) }
|
7
|
-
|
8
|
-
it "accepts a manager and a fetch strategy" do
|
9
|
-
fetcher = Fetcher.new(manager, fetch)
|
10
|
-
fetcher.mgr.should == manager
|
11
|
-
fetcher.strategy.should == fetch
|
12
|
-
end
|
13
|
-
|
14
|
-
describe "#fetch" do
|
15
|
-
let(:fetcher) { Fetcher.new(manager, fetch) }
|
16
|
-
|
17
|
-
it "retrieves work from fetch strategy" do
|
18
|
-
fetch.should_receive(:retrieve_work)
|
19
|
-
fetcher.fetch
|
20
|
-
end
|
21
|
-
|
22
|
-
it "tells manager to assign work if work is fetched" do
|
23
|
-
work = mock(:work)
|
24
|
-
fetch.stub(retrieve_work: work)
|
25
|
-
manager.stub(async: manager)
|
26
|
-
manager.should_receive(:assign).with(work)
|
27
|
-
fetcher.fetch
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|