proletariat 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/README.md +93 -0
- data/Rakefile +1 -0
- data/lib/proletariat/concerns/logging.rb +17 -0
- data/lib/proletariat/cucumber.rb +20 -0
- data/lib/proletariat/manager.rb +99 -0
- data/lib/proletariat/publisher.rb +87 -0
- data/lib/proletariat/queue_config.rb +26 -0
- data/lib/proletariat/runner.rb +149 -0
- data/lib/proletariat/subscriber.rb +249 -0
- data/lib/proletariat/testing/expectation.rb +15 -0
- data/lib/proletariat/testing/expectation_guarantor.rb +145 -0
- data/lib/proletariat/testing/fixnum_extension.rb +10 -0
- data/lib/proletariat/testing.rb +41 -0
- data/lib/proletariat/version.rb +4 -0
- data/lib/proletariat/worker.rb +131 -0
- data/lib/proletariat.rb +87 -0
- data/proletariat.gemspec +24 -0
- data/spec/lib/proletariat_spec.rb +57 -0
- metadata +121 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
module Proletariat
|
2
|
+
# Internal: Creates, binds and listens on a RabbitMQ queue. Forwards
|
3
|
+
# messages to a given listener.
|
4
|
+
class Subscriber
|
5
|
+
include Concurrent::Runnable
|
6
|
+
|
7
|
+
include Concerns::Logging
|
8
|
+
|
9
|
+
# Public: Creates a new Subscriber instance.
|
10
|
+
#
|
11
|
+
# connection - An open Bunny::Session object.
|
12
|
+
# exchange_name - A String of the RabbitMQ topic exchange.
|
13
|
+
# queue_config - A QueueConfig value object.
|
14
|
+
def initialize(connection, listener, queue_config)
|
15
|
+
@connection = connection
|
16
|
+
@listener = listener
|
17
|
+
@queue_config = queue_config
|
18
|
+
|
19
|
+
@channel = @connection.create_channel
|
20
|
+
|
21
|
+
@channel.prefetch queue_config.prefetch
|
22
|
+
|
23
|
+
@exchange = @channel.topic queue_config.exchange_name, durable: true
|
24
|
+
@bunny_queue = @channel.queue queue_config.queue_name,
|
25
|
+
durable: true,
|
26
|
+
auto_delete: queue_config.auto_delete
|
27
|
+
|
28
|
+
bind_queue
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Called by the Concurrent framework on run. Used here to start
|
32
|
+
# consumption of the queue and to log the status of the
|
33
|
+
# subscriber.
|
34
|
+
#
|
35
|
+
# Returns nil.
|
36
|
+
def on_run
|
37
|
+
start_consumer
|
38
|
+
log_info 'Now online'
|
39
|
+
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Internal: Called by the Concurrent framework on run. Used here to stop
|
44
|
+
# consumption of the queue and to log the status of the
|
45
|
+
# subscriber.
|
46
|
+
#
|
47
|
+
# Returns nil.
|
48
|
+
def on_stop
|
49
|
+
log_info 'Attempting graceful shutdown.'
|
50
|
+
stop_consumer
|
51
|
+
log_info 'Now offline'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Internal: Called by the Concurrent framework to perform work. Used here
|
55
|
+
# acknowledge RabbitMQ messages.
|
56
|
+
#
|
57
|
+
# Returns nil.
|
58
|
+
def on_task
|
59
|
+
ready_acknowledgers.each do |acknowledger|
|
60
|
+
acknowledger.acknowledge_on_channel channel
|
61
|
+
acknowledgers.delete acknowledger
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: Purge the RabbitMQ queue.
|
66
|
+
#
|
67
|
+
# Returns nil.
|
68
|
+
def purge
|
69
|
+
bunny_queue.purge
|
70
|
+
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Internal: Returns the Bunny::Queue in use.
|
77
|
+
attr_reader :bunny_queue
|
78
|
+
|
79
|
+
# Internal: Returns the Bunny::Channel in use.
|
80
|
+
attr_reader :channel
|
81
|
+
|
82
|
+
# Internal: Returns the Bunny::Exchange in use.
|
83
|
+
attr_reader :exchange
|
84
|
+
|
85
|
+
# Internal: Returns the listener object.
|
86
|
+
attr_reader :listener
|
87
|
+
|
88
|
+
# Internal: Returns the queue_config in use.
|
89
|
+
attr_reader :queue_config
|
90
|
+
|
91
|
+
def acknowledgers
|
92
|
+
@acknowledgers ||= []
|
93
|
+
end
|
94
|
+
|
95
|
+
# Internal: Binds bunny_queue to the exchange via each routing key
|
96
|
+
# specified in the queue_config.
|
97
|
+
#
|
98
|
+
# Returns nil.
|
99
|
+
def bind_queue
|
100
|
+
queue_config.routing_keys.each do |key|
|
101
|
+
bunny_queue.bind exchange, routing_key: key
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Internal: Get acknowledgers for messages whose work has completed.
|
108
|
+
#
|
109
|
+
# Returns an Array of Acknowledgers.
|
110
|
+
def ready_acknowledgers
|
111
|
+
acknowledgers.select do |acknowledger|
|
112
|
+
acknowledger.ready_to_acknowledge?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Internal: Starts a consumer on the queue. The consumer forwards all
|
117
|
+
# message bodies to listener#post.
|
118
|
+
#
|
119
|
+
# Returns nil.
|
120
|
+
def start_consumer
|
121
|
+
@consumer = bunny_queue.subscribe ack: true do |info, properties, body|
|
122
|
+
future = listener.post?(body)
|
123
|
+
acknowledgers << Acknowledger.new(future, info.delivery_tag)
|
124
|
+
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
|
131
|
+
# Internal: Stops any active consumer. Waits for acknowledgement queue to
|
132
|
+
# drain before returning.
|
133
|
+
#
|
134
|
+
# Returns nil.
|
135
|
+
def stop_consumer
|
136
|
+
@consumer.cancel if @consumer
|
137
|
+
wait_for_acknowledgers if acknowledgers.any?
|
138
|
+
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Internal: Makes blocking calls for each unacknowledged message until all
|
143
|
+
# messages are acknowledged.
|
144
|
+
#
|
145
|
+
# Returns nil.
|
146
|
+
def wait_for_acknowledgers
|
147
|
+
log_info 'Waiting for unacknowledged messages.'
|
148
|
+
while acknowledgers.any?
|
149
|
+
acknowledger = acknowledgers.pop
|
150
|
+
acknowledger.block_until_acknowledged channel
|
151
|
+
end
|
152
|
+
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
|
156
|
+
# Internal: Used to watch the state of dispatched Work and send ack/nack
|
157
|
+
# to a RabbitMQ channel.
|
158
|
+
class Acknowledger
|
159
|
+
# Public: Maximum time in seconds to wait synchronously for an
|
160
|
+
# acknowledgement.
|
161
|
+
MAX_BLOCK_TIME = 5
|
162
|
+
|
163
|
+
# Public: Creates a new Acknowledger instance.
|
164
|
+
#
|
165
|
+
# future - A future-like object holding the Worker response.
|
166
|
+
# delivery_tag - The RabbitMQ delivery tag to be used when ack/nacking.
|
167
|
+
def initialize(future, delivery_tag)
|
168
|
+
@future = future
|
169
|
+
@delivery_tag = delivery_tag
|
170
|
+
end
|
171
|
+
|
172
|
+
# Public: Retrieves the value from the future and sends the relevant
|
173
|
+
# acknowledgement on a given channel. Logs a warning if the
|
174
|
+
# future value is unexpected.
|
175
|
+
#
|
176
|
+
# channel - The Bunny::Channel to receive the acknowledgement.
|
177
|
+
#
|
178
|
+
# Returns nil.
|
179
|
+
def acknowledge_on_channel(channel)
|
180
|
+
if future.fulfilled?
|
181
|
+
acknowledge_success(channel)
|
182
|
+
elsif future.rejected?
|
183
|
+
acknowledge_error(channel)
|
184
|
+
end
|
185
|
+
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
|
189
|
+
# Public: Blocks until acknowledgement completes.
|
190
|
+
#
|
191
|
+
# channel - The Bunny::Channel to receive the acknowledgement.
|
192
|
+
#
|
193
|
+
# Returns nil.
|
194
|
+
def block_until_acknowledged(channel)
|
195
|
+
future.value(MAX_BLOCK_TIME)
|
196
|
+
acknowledge_on_channel(channel)
|
197
|
+
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# Public: Gets the readiness of the future for acknowledgement use.
|
202
|
+
#
|
203
|
+
# Returns true if future is fulfilled or rejected.
|
204
|
+
def ready_to_acknowledge?
|
205
|
+
future.state != :pending
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
# Internal: Dispatches acknowledgements for non-errored worker responses.
|
211
|
+
# Maps symbol value to acknowledgement strategies.
|
212
|
+
#
|
213
|
+
# channel - The Bunny::Channel to receive the acknowledgement.
|
214
|
+
#
|
215
|
+
# Returns nil.
|
216
|
+
def acknowledge_success(channel)
|
217
|
+
case future.value
|
218
|
+
when :ok then channel.acknowledge delivery_tag
|
219
|
+
when :drop then channel.reject delivery_tag, false
|
220
|
+
when :requeue then channel.reject delivery_tag, true
|
221
|
+
else
|
222
|
+
Proletariat.logger.warn 'Unexpected return value from #work.'
|
223
|
+
channel.reject delivery_tag, false
|
224
|
+
end
|
225
|
+
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
# Internal: Dispatches acknowledgements for errored worker responses.
|
230
|
+
# Requeues messages and logs the error.
|
231
|
+
#
|
232
|
+
# channel - The Bunny::Channel to receive the acknowledgement.
|
233
|
+
#
|
234
|
+
# Returns nil.
|
235
|
+
def acknowledge_error(channel)
|
236
|
+
Proletariat.logger.error future.reason
|
237
|
+
channel.reject delivery_tag, true
|
238
|
+
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
|
242
|
+
# Internal: Returns the RabbitMQ delivery tag.
|
243
|
+
attr_reader :delivery_tag
|
244
|
+
|
245
|
+
# Internal: Returns the future-like object holding the Worker response.
|
246
|
+
attr_reader :future
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Proletariat
|
2
|
+
module Testing
|
3
|
+
# Internal: Defines a quantity of messages you expect to receive on a set
|
4
|
+
# of topics.
|
5
|
+
class Expectation < Struct.new(:topics, :quantity)
|
6
|
+
# Public: Builds a new duplicate of current instance with different
|
7
|
+
# topics.
|
8
|
+
#
|
9
|
+
# Returns a new instance of Expectation.
|
10
|
+
def on_topic(*topics)
|
11
|
+
Expectation.new(topics, quantity)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Proletariat
|
2
|
+
module Testing
|
3
|
+
# Internal: Executes a block and ensures given expectations are satisfied
|
4
|
+
# before continuing.
|
5
|
+
class ExpectationGuarantor
|
6
|
+
# Public: An error which will be raised if the expectation is not
|
7
|
+
# satisfied within the timeout.
|
8
|
+
class MessageTimeoutError < RuntimeError; end
|
9
|
+
|
10
|
+
# Public: Default time to wait for expectation to be satisfied.
|
11
|
+
MESSAGE_TIMEOUT = 10
|
12
|
+
|
13
|
+
# Public: Interval at which to check expectation is satisfied.
|
14
|
+
MESSAGE_CHECK_INTERVAL = 0.2
|
15
|
+
|
16
|
+
# Public: Creates a new ExpectationGuarantor instance.
|
17
|
+
#
|
18
|
+
# expectations - An Array of Expectations to be checked.
|
19
|
+
# block - The block of code within which the expectations should
|
20
|
+
# be satisfied.
|
21
|
+
def initialize(expectations, &block)
|
22
|
+
@connection = Proletariat.runner.connection
|
23
|
+
@counters = []
|
24
|
+
@subscribers = []
|
25
|
+
|
26
|
+
expectations.each do |expectation|
|
27
|
+
queue_config = generate_queue_config_for_topic(expectation.topics)
|
28
|
+
counter = MessageCounter.new(expectation.quantity)
|
29
|
+
counters << counter
|
30
|
+
subscribers << Subscriber.new(connection, counter, queue_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
@block = block
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Execute the blocks and waits for the expectations to be met.
|
37
|
+
#
|
38
|
+
# Returns nil if expectations are met within timeout.
|
39
|
+
# Raises MessageTimeoutError if expectations are not met within timeout.
|
40
|
+
def guarantee
|
41
|
+
run_subscribers
|
42
|
+
|
43
|
+
block.call
|
44
|
+
|
45
|
+
timer = 0.0
|
46
|
+
|
47
|
+
until passed?
|
48
|
+
fail MessageTimeoutError if timer > MESSAGE_TIMEOUT
|
49
|
+
sleep MESSAGE_CHECK_INTERVAL
|
50
|
+
timer += MESSAGE_CHECK_INTERVAL
|
51
|
+
end
|
52
|
+
|
53
|
+
stop_subscribers
|
54
|
+
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Internal: Returns the block of code in which the expectations should be
|
61
|
+
# satisfied.
|
62
|
+
attr_reader :block
|
63
|
+
|
64
|
+
# Internal: Returns an open Bunny::Session object.
|
65
|
+
attr_reader :connection
|
66
|
+
|
67
|
+
# Internal: Returns an array of MessageCounter instances.
|
68
|
+
attr_reader :counters
|
69
|
+
|
70
|
+
# Internal: Returns an array of Subscriber instances.
|
71
|
+
attr_reader :subscribers
|
72
|
+
|
73
|
+
def generate_queue_config_for_topic(topics)
|
74
|
+
QueueConfig.new('', Proletariat.runner.exchange_name, topics, 1, true)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Internal: Checks each counter to ensure expected messages have arrived.
|
78
|
+
#
|
79
|
+
# Returns true if all counters are satisfied.
|
80
|
+
# Returns false if one or more counters are not satisfied.
|
81
|
+
def passed?
|
82
|
+
counters
|
83
|
+
.map(&:expected_messages_received?)
|
84
|
+
.reduce { |a, e| a && e }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Internal: Starts each subscriber.
|
88
|
+
#
|
89
|
+
# Returns nil.
|
90
|
+
def run_subscribers
|
91
|
+
subscribers.each { |subscriber| subscriber.run! }
|
92
|
+
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Internal: Stops each subscriber.
|
97
|
+
#
|
98
|
+
# Returns nil.
|
99
|
+
def stop_subscribers
|
100
|
+
subscribers.each { |subscriber| subscriber.stop }
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Internal: Counts incoming messages to test expection satisfaction.
|
106
|
+
class MessageCounter
|
107
|
+
# Public: Creates a new MessageCounter instance.
|
108
|
+
#
|
109
|
+
# expected - The number of messages expected.
|
110
|
+
def initialize(expected)
|
111
|
+
@count = 0
|
112
|
+
@expected = expected
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Checks whether message count satifies expected count.
|
116
|
+
#
|
117
|
+
# Returns true if count is greater or equal to expected.
|
118
|
+
# Returns false if count less than expected.
|
119
|
+
def expected_messages_received?
|
120
|
+
count >= expected
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: Handles message calls from a subscriber and increments the
|
124
|
+
# count. Return value matches interface expected by Subscriber.
|
125
|
+
#
|
126
|
+
# message - The contents of the message.
|
127
|
+
#
|
128
|
+
# Returns a future-like object holding an :ok Symbol.
|
129
|
+
def post?(message)
|
130
|
+
self.count = count + 1
|
131
|
+
|
132
|
+
Concurrent::Future.new { :ok }
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Internal: Returns the current message count.
|
138
|
+
attr_accessor :count
|
139
|
+
|
140
|
+
# Internal: Returns the expected message count.
|
141
|
+
attr_reader :expected
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Public: Extends Fixnum to provide sugar for creating Expectation instances.
|
2
|
+
class Fixnum
|
3
|
+
# Public: Builds an Expectation instance which listens for a quantity of
|
4
|
+
# messages equal to self on any topic.
|
5
|
+
#
|
6
|
+
# Returns a new Expectation instance.
|
7
|
+
def messages
|
8
|
+
Proletariat::Testing::Expectation.new(['#'], self)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'proletariat/testing/expectation'
|
2
|
+
require 'proletariat/testing/expectation_guarantor'
|
3
|
+
require 'proletariat/testing/fixnum_extension'
|
4
|
+
|
5
|
+
module Proletariat
|
6
|
+
# Public: Mixin to aid solve test synchronization issues while still running
|
7
|
+
# Proletariat the same way you would in production,
|
8
|
+
module Testing
|
9
|
+
# Public: Builds an Expectation instance which listens for a single message
|
10
|
+
# on any topic.
|
11
|
+
#
|
12
|
+
# Returns a new Expectation instance.
|
13
|
+
def message
|
14
|
+
Proletariat::Testing::Expectation.new(['#'], 1)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Creates and runs a new ExpectationGuarantor from a given list of
|
18
|
+
# Expectation instances and a block.
|
19
|
+
#
|
20
|
+
# expectations - One or more Expectation instances.
|
21
|
+
# block - A block within which the expectations should be
|
22
|
+
# satisfied.
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# wait_for 3.messages.on_topic 'email_sent'
|
27
|
+
# # ... [Time passes]
|
28
|
+
# # => 'nil'
|
29
|
+
#
|
30
|
+
# wait_for message.on_topic 'hell_freezes_over'
|
31
|
+
# # ... [Time passes]
|
32
|
+
# # => MessageTimeoutError
|
33
|
+
#
|
34
|
+
# Returns nil.
|
35
|
+
def wait_for(*expectations, &block)
|
36
|
+
ExpectationGuarantor.new(expectations, &block).guarantee
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Proletariat
|
2
|
+
# Public: Handles messages from a RabbitMQ queue. Subclasses should
|
3
|
+
# overwrite the #work method.
|
4
|
+
class Worker < Concurrent::Actor
|
5
|
+
include Concerns::Logging
|
6
|
+
|
7
|
+
# Internal: Called by the Concurrent framework to handle new mailbox
|
8
|
+
# messages. Overridden in this subclass to call the #work method
|
9
|
+
# with the given message.
|
10
|
+
#
|
11
|
+
# message - The incoming message.
|
12
|
+
#
|
13
|
+
# Returns nil.
|
14
|
+
def act(message)
|
15
|
+
work message
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal: Called by the Concurrent framework on actor start. Overridden
|
19
|
+
# in this subclass to log the status of the worker.
|
20
|
+
#
|
21
|
+
# Returns nil.
|
22
|
+
def on_run
|
23
|
+
super
|
24
|
+
|
25
|
+
log_info 'Now online'
|
26
|
+
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Called by the Concurrent framework on actor start. Overridden
|
31
|
+
# in this subclass to log the status of the worker.
|
32
|
+
#
|
33
|
+
# Returns nil.
|
34
|
+
def on_stop
|
35
|
+
log_info 'Attempting graceful shutdown.'
|
36
|
+
wait_for_work_queue unless queue.empty?
|
37
|
+
|
38
|
+
super
|
39
|
+
|
40
|
+
log_info 'Now offline'
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Handles RabbitMQ messages.
|
46
|
+
#
|
47
|
+
# message - The incoming message.
|
48
|
+
#
|
49
|
+
# Raises NotImplementedError unless implemented in subclass.
|
50
|
+
def work(message)
|
51
|
+
fail NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Public: Helper method to ease accessing the logger from within #work.
|
57
|
+
# Sends #info to logger if message provided.
|
58
|
+
#
|
59
|
+
# Examples
|
60
|
+
#
|
61
|
+
# log 'Background Workers Unite!'
|
62
|
+
# # Message is logged at info level.
|
63
|
+
#
|
64
|
+
# log.error 'Something bad happened!'
|
65
|
+
# # Message is logged at error level.
|
66
|
+
#
|
67
|
+
# Returns the process-wide logger if message not supplied.
|
68
|
+
# Returns nil if message supplied.
|
69
|
+
def log(message = nil)
|
70
|
+
if message
|
71
|
+
Proletariat.logger.info(message)
|
72
|
+
|
73
|
+
nil
|
74
|
+
else
|
75
|
+
Proletariat.logger
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: Helper method to ease sending messages from within #work.
|
80
|
+
#
|
81
|
+
# to - The routing key for the message to as a String. In accordance
|
82
|
+
# with the RabbitMQ convention you can use the '*' character to
|
83
|
+
# replace one word and the '#' to replace many words.
|
84
|
+
# message - The message as a String.
|
85
|
+
#
|
86
|
+
# Returns nil.
|
87
|
+
def publish(to, message = '')
|
88
|
+
Proletariat.publish to, message
|
89
|
+
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Internal: Blocks until each message has been handled by #work.
|
96
|
+
#
|
97
|
+
# Returns nil.
|
98
|
+
def wait_for_work_queue
|
99
|
+
log_info 'Waiting for work queue to drain.'
|
100
|
+
|
101
|
+
work(*queue.pop.message) until queue.empty?
|
102
|
+
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Internal: Class methods on Worker to provide configuration DSL.
|
107
|
+
module ConfigurationMethods
|
108
|
+
# Public: A configuration method for adding a routing key to be used when
|
109
|
+
# binding this worker type's queue to an exchange.
|
110
|
+
#
|
111
|
+
# routing_key - A routing key for queue-binding as a String.
|
112
|
+
#
|
113
|
+
# Returns nil.
|
114
|
+
def listen_on(routing_key)
|
115
|
+
routing_keys << routing_key
|
116
|
+
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Internal: Returns the list of all desired routing keys for this worker
|
121
|
+
# type
|
122
|
+
#
|
123
|
+
# Returns an Array of routing keys as Strings.
|
124
|
+
def routing_keys
|
125
|
+
@routing_keys ||= []
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
extend ConfigurationMethods
|
130
|
+
end
|
131
|
+
end
|
data/lib/proletariat.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'proletariat/version'
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require 'bunny'
|
5
|
+
require 'logger'
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
require 'proletariat/concerns/logging'
|
9
|
+
|
10
|
+
require 'proletariat/manager'
|
11
|
+
require 'proletariat/publisher'
|
12
|
+
require 'proletariat/queue_config'
|
13
|
+
require 'proletariat/runner'
|
14
|
+
require 'proletariat/subscriber'
|
15
|
+
require 'proletariat/worker'
|
16
|
+
|
17
|
+
# Public: Creates the Proletariat namespace and holds a process-wide Runner
|
18
|
+
# instance as well as a logger.
|
19
|
+
module Proletariat
|
20
|
+
# Public: The default name used for the RabbitMQ topic exchange.
|
21
|
+
DEFAULT_EXCHANGE_NAME = 'proletariat'
|
22
|
+
|
23
|
+
class << self
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
# Public: Delegate lifecycle calls to the process-wide Runner.
|
27
|
+
def_delegators :runner, :run, :run!, :stop, :running?, :publish, :purge
|
28
|
+
|
29
|
+
# Public: Allows the setting of an alternate logger.
|
30
|
+
#
|
31
|
+
# logger - An object which fulfills the role of a Logger.
|
32
|
+
attr_writer :logger
|
33
|
+
|
34
|
+
# Public: Sets the process-wide Runner to an instance initialized with a
|
35
|
+
# given hash of options.
|
36
|
+
#
|
37
|
+
# options - A Hash of options (default: {}):
|
38
|
+
# :connection - An open RabbitMQ::Session object.
|
39
|
+
# :exchange_name - The RabbitMQ topic exchange name as a
|
40
|
+
# String.
|
41
|
+
# :logger - An object which fulfills the role of a
|
42
|
+
# Logger.
|
43
|
+
# :publisher_threads - The size of the publisher thread pool.
|
44
|
+
# :supervisor - A Supervisor instance.
|
45
|
+
# :worker_classes - An Array of Worker subclasses.
|
46
|
+
# :worker_threads - The size of the worker thread pool.
|
47
|
+
def configure(options = {})
|
48
|
+
self.logger = options.fetch(:logger, default_logger)
|
49
|
+
|
50
|
+
@runner = Runner.new(defaults.merge(options))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Internal: The logger used if no other is specified via .configure.
|
54
|
+
#
|
55
|
+
# Returns a Logger which logs to STDOUT.
|
56
|
+
def default_logger
|
57
|
+
Logger.new(STDOUT)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Internal: Default process-wide Runner options.
|
61
|
+
#
|
62
|
+
# Returns a Hash of options.
|
63
|
+
def defaults
|
64
|
+
{
|
65
|
+
worker_classes: workers_from_env || []
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def logger
|
70
|
+
@logger ||= default_logger
|
71
|
+
end
|
72
|
+
|
73
|
+
def runner
|
74
|
+
@runner ||= Runner.new(defaults)
|
75
|
+
end
|
76
|
+
|
77
|
+
def workers_from_env
|
78
|
+
if ENV['WORKERS']
|
79
|
+
ENV['WORKERS'].split(',').map(&:strip).map do |string|
|
80
|
+
string
|
81
|
+
.split('::')
|
82
|
+
.reduce(Object) { |a, e| a.const_get(e) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|