leveret 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/leveret/configuration.rb +14 -1
- data/lib/leveret/delay_queue.rb +37 -0
- data/lib/leveret/job.rb +9 -5
- data/lib/leveret/message.rb +27 -0
- data/lib/leveret/queue.rb +5 -6
- data/lib/leveret/result_handler.rb +58 -0
- data/lib/leveret/version.rb +1 -1
- data/lib/leveret/worker.rb +9 -31
- data/lib/leveret.rb +8 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c09edf84323e1f658a68c1a63e642fbe6a24570e
|
4
|
+
data.tar.gz: d81b7f5a2637e8a3b6831db528a0aed27b17458e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94522b76698bc0bac4d171aad0b9a91a2ff9c7ded392539a78c450e97a4e910cc796258ca2f94d535a8df6a84eebe74f01dca445e5d093bec93fa5700b54eb6d
|
7
|
+
data.tar.gz: c5b6f7493a901b39a6abe11d9de9df0b3d05460915907cc534249220afec2766c2cebe4cc1d66ab9ed312ff9bc7c5e80d27e5a5f07d3acf93ecf56b4f4e1bf53
|
@@ -9,6 +9,16 @@ module Leveret
|
|
9
9
|
# @!attribute queue_name_prefix
|
10
10
|
# @return [String] This value will be prefixed to all queues created on your RabbitMQ instance.
|
11
11
|
# Default: +"leveret_queue"+
|
12
|
+
# @!attribute delay_exchange_name
|
13
|
+
# @return [String] Name of the exchange for Leveret to publish to that should be processed later.
|
14
|
+
# Default: +"leveret_delay_exch"+
|
15
|
+
# @!attribute delay_queue_name
|
16
|
+
# @return [String] Name of the queue for Leveret to use to store messages that should be processed later.
|
17
|
+
# Default: +"leveret_delay_queue"+
|
18
|
+
# @!attribute delay_time
|
19
|
+
# @return [Integer] Amount of time a delayed message should say on the delay queue in milliseconds Default: +10000+
|
20
|
+
# @!attribute delay_exchange_name
|
21
|
+
# @return [String] Name of the exchange for Leveret to publish messages to. Default: +"leveret_exch"+
|
12
22
|
# @!attribute log_file
|
13
23
|
# @return [String] The path where logfiles should be written to. Default: +STDOUT+
|
14
24
|
# @!attribute log_level
|
@@ -24,7 +34,7 @@ module Leveret
|
|
24
34
|
# @return [Integer] The number of jobs that can be processes simultanously. Default: +1+
|
25
35
|
class Configuration
|
26
36
|
attr_accessor :amqp, :exchange_name, :queue_name_prefix, :log_file, :log_level, :default_queue_name, :after_fork,
|
27
|
-
:error_handler, :concurrent_fork_count
|
37
|
+
:error_handler, :concurrent_fork_count, :delay_exchange_name, :delay_queue_name, :delay_time
|
28
38
|
|
29
39
|
# Create a new instance of Configuration with a set of sane defaults.
|
30
40
|
def initialize
|
@@ -40,6 +50,9 @@ module Leveret
|
|
40
50
|
self.log_level = Logger::DEBUG
|
41
51
|
self.queue_name_prefix = 'leveret_queue'
|
42
52
|
self.default_queue_name = 'standard'
|
53
|
+
self.delay_exchange_name = 'leveret_delay_exch'
|
54
|
+
self.delay_queue_name = 'leveret_delay_queue'
|
55
|
+
self.delay_time = 10_000
|
43
56
|
self.after_fork = proc {}
|
44
57
|
self.error_handler = proc { |ex| ex }
|
45
58
|
self.concurrent_fork_count = 1
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Connects to a special queue which keeps messages in a holding pattern until a timeout expires and then
|
3
|
+
# publishes those messages back to the main queue for processing.
|
4
|
+
class DelayQueue
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :queue
|
8
|
+
|
9
|
+
def_delegators :Leveret, :configuration, :channel, :log
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@queue = connect_to_queue
|
13
|
+
end
|
14
|
+
|
15
|
+
# Place a message onto the delay queue, which will later be expired and sent back to the main exchange
|
16
|
+
#
|
17
|
+
# @param [Message] A message received and processed already
|
18
|
+
def republish(message)
|
19
|
+
delay_exchange.publish(message.params.serialize, expiration: configuration.delay_time, persistent: true,
|
20
|
+
routing_key: message.routing_key, priority: message.priority)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def connect_to_queue
|
26
|
+
queue = channel.queue(configuration.delay_queue_name, durable: true,
|
27
|
+
arguments: { 'x-dead-letter-exchange': configuration.exchange_name })
|
28
|
+
queue.bind(delay_exchange)
|
29
|
+
log.info "Connected to #{configuration.delay_queue_name}, bound to #{configuration.delay_exchange_name}"
|
30
|
+
queue
|
31
|
+
end
|
32
|
+
|
33
|
+
def delay_exchange
|
34
|
+
@delay_exchange ||= channel.exchange(Leveret.configuration.delay_exchange_name, type: :fanout, durable: :true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/leveret/job.rb
CHANGED
@@ -36,14 +36,15 @@ module Leveret
|
|
36
36
|
# MyJob.enqueue(test_text: "Hi there, please write this different text to the file", queue_name: 'other_queue')
|
37
37
|
#
|
38
38
|
module Job
|
39
|
-
# Raise this when your job has failed, but try again
|
40
|
-
# available again.
|
39
|
+
# Raise this when your job has failed, but try again as soon as another worker is available.
|
41
40
|
class RequeueJob < StandardError; end
|
42
41
|
|
43
|
-
# Raise this when your job has failed, but you don't want to requeue it
|
44
|
-
# and try again.
|
42
|
+
# Raise this when your job has failed, but you don't want to requeue it and try again.
|
45
43
|
class RejectJob < StandardError; end
|
46
44
|
|
45
|
+
# Raise thie when you want your job to be executed later (later is defined in {Leveret.configuration.delay_time})
|
46
|
+
class DelayJob < StandardError; end
|
47
|
+
|
47
48
|
# Instance methods to mixin with your job
|
48
49
|
module InstanceMethods
|
49
50
|
# @!attribute params
|
@@ -59,7 +60,7 @@ module Leveret
|
|
59
60
|
|
60
61
|
# Runs the job and captures any exceptions to turn them into symbols which represent the status of the job
|
61
62
|
#
|
62
|
-
# @return [Symbol] :success, :requeue, :reject depending on job success
|
63
|
+
# @return [Symbol] :success, :requeue, :reject, :delay depending on job success
|
63
64
|
def run
|
64
65
|
Leveret.log.info "Running #{self.class.name} with #{params}"
|
65
66
|
perform
|
@@ -70,6 +71,9 @@ module Leveret
|
|
70
71
|
rescue Leveret::Job::RejectJob
|
71
72
|
Leveret.log.warn "Rejecting job #{self.class.name} with #{params}"
|
72
73
|
:reject
|
74
|
+
rescue Leveret::Job::DelayJob
|
75
|
+
Leveret.log.warn "Delaying job #{self.class.name} with #{params}"
|
76
|
+
:delay
|
73
77
|
rescue StandardError => e
|
74
78
|
Leveret.log.error "#{e.message} when processing #{self.class.name} with #{params}"
|
75
79
|
Leveret.configuration.error_handler.call(e, self)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Roll up all of the separate parts of an incoming message into a nice convenient object to move around
|
3
|
+
#
|
4
|
+
# @!attribute delivery_info
|
5
|
+
# @return [Bunny::DeliveryInfo] Full of useful things like the delivery_tag and routing key
|
6
|
+
# @!attribute properties
|
7
|
+
# @return [Delivery::Properties] Full of useful things like content-type and priority
|
8
|
+
# @!attribute params
|
9
|
+
# @return [Parameters] Deserialized params
|
10
|
+
class Message
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_accessor :delivery_info, :properties, :params
|
14
|
+
|
15
|
+
def_delegators :delivery_info, :delivery_tag, :routing_key
|
16
|
+
def_delegators :properties, :priority
|
17
|
+
|
18
|
+
# @param [Bunny::DeliveryInfo] delivery_info Full of useful things like the delivery_tag and routing key
|
19
|
+
# @param [Bunny::Properties] properties Full of useful things like content-type and priority
|
20
|
+
# @param [Parameters] Deserialized params parsed from the message
|
21
|
+
def initialize(delivery_info, properties, params)
|
22
|
+
self.delivery_info = delivery_info
|
23
|
+
self.properties = properties
|
24
|
+
self.params = params
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/leveret/queue.rb
CHANGED
@@ -41,7 +41,7 @@ module Leveret
|
|
41
41
|
payload = serialize_payload(payload)
|
42
42
|
|
43
43
|
log.debug "Publishing #{payload.inspect} for queue #{name} (Priority: #{priority_id})"
|
44
|
-
|
44
|
+
exchange.publish(payload, persistent: true, routing_key: name, priority: priority_id)
|
45
45
|
end
|
46
46
|
|
47
47
|
# Subscribe to this queue and yield a block for every message received. This method does not block, receiving and
|
@@ -53,16 +53,15 @@ module Leveret
|
|
53
53
|
#
|
54
54
|
# @note The receiving block is responsible for acking/rejecting the message. Please see the note for more details.
|
55
55
|
#
|
56
|
-
# @yieldparam
|
57
|
-
# @yieldparam delivery_tag [String] The identifier for this message that must be used do ack/reject the message
|
58
|
-
# @yieldparam payload [Parameters] A deserialized version of the payload contained in the message
|
56
|
+
# @yieldparam incoming [Message] Delivery info, properties and the params wrapped up into a convenient object
|
59
57
|
#
|
60
58
|
# @return [void]
|
61
59
|
def subscribe
|
62
60
|
log.info "Subscribing to #{name}"
|
63
|
-
queue.subscribe(manual_ack: true) do |delivery_info,
|
61
|
+
queue.subscribe(manual_ack: true) do |delivery_info, properties, msg|
|
64
62
|
log.debug "Received #{msg} from #{name}"
|
65
|
-
|
63
|
+
incoming = Leveret::Message.new(delivery_info, properties, deserialize_payload(msg))
|
64
|
+
yield incoming
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Handles the acknowledgement or rejection of messages after execution
|
3
|
+
class ResultHandler
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_accessor :incoming_message
|
7
|
+
|
8
|
+
def_delegators :Leveret, :log, :delay_queue
|
9
|
+
|
10
|
+
# @param [Message] incoming_message Contains delivery information such as the delivery_tag
|
11
|
+
def initialize(incoming_message)
|
12
|
+
self.incoming_message = incoming_message
|
13
|
+
end
|
14
|
+
|
15
|
+
# Call the appropriate handling method for the result
|
16
|
+
#
|
17
|
+
# @param [Symbol] result Result returned from running the job, one of +:success+, +:reject+ or +:requeue+
|
18
|
+
def handle(result)
|
19
|
+
log.info "[#{delivery_tag}] Job returned #{result}"
|
20
|
+
send(result) if [:success, :reject, :requeue, :delay].include?(result)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Mark the message as acknowledged
|
24
|
+
def success
|
25
|
+
log.debug "[#{delivery_tag}] Acknowledging message"
|
26
|
+
channel.acknowledge(delivery_tag)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Mark the message as rejected (failure)
|
30
|
+
def reject
|
31
|
+
log.debug "[#{delivery_tag}] Rejecting message"
|
32
|
+
channel.reject(delivery_tag)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Reject the message and reinsert it onto it's queue
|
36
|
+
def requeue
|
37
|
+
log.debug "[#{delivery_tag}] Requeueing message"
|
38
|
+
channel.reject(delivery_tag, true)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Acknowledge the message, but publish it onto the delay queue for execution later
|
42
|
+
def delay
|
43
|
+
log.debug ["[#{delivery_tag}] Delaying message"]
|
44
|
+
channel.acknowledge(delivery_tag)
|
45
|
+
delay_queue.republish(incoming_message)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def channel
|
51
|
+
incoming_message.delivery_info.channel
|
52
|
+
end
|
53
|
+
|
54
|
+
def delivery_tag
|
55
|
+
incoming_message.delivery_info.delivery_tag
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/leveret/version.rb
CHANGED
data/lib/leveret/worker.rb
CHANGED
@@ -69,8 +69,8 @@ module Leveret
|
|
69
69
|
# allow us to gracefully cancel these subscriptions when we need to quit.
|
70
70
|
def start_subscriptions
|
71
71
|
queues.map do |queue|
|
72
|
-
consumers << queue.subscribe do |
|
73
|
-
fork_and_run(
|
72
|
+
consumers << queue.subscribe do |incoming_message|
|
73
|
+
fork_and_run(incoming_message)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -88,23 +88,20 @@ module Leveret
|
|
88
88
|
# Detach the main process from the child so we can return to the main loop without waiting for it to finish
|
89
89
|
# processing the job.
|
90
90
|
#
|
91
|
-
# @param [
|
92
|
-
|
93
|
-
# or reject the message after processing.
|
94
|
-
# @param [Parameters] payload The job name and parameters the job requires
|
95
|
-
def fork_and_run(channel, delivery_tag, payload)
|
91
|
+
# @param [Message] payload Message meta and payload to process
|
92
|
+
def fork_and_run(incoming_message)
|
96
93
|
pid = fork do
|
97
94
|
self.process_name = 'leveret-worker-child'
|
98
|
-
log.info "[#{delivery_tag}] Forked to child process #{pid} to run #{payload[:job]}"
|
95
|
+
log.info "[#{incoming_message.delivery_tag}] Forked to child process #{pid} to run #{payload[:job]}"
|
99
96
|
|
100
97
|
Leveret.reset_connection!
|
101
98
|
Leveret.configuration.after_fork.call
|
102
99
|
|
103
|
-
result = perform_job(
|
104
|
-
|
105
|
-
|
100
|
+
result = perform_job(incoming_message.params)
|
101
|
+
result_handler = Leveret::ResultHandler.new(incoming_message)
|
102
|
+
result_handler.handle(result)
|
106
103
|
|
107
|
-
log.info "[#{delivery_tag}] Exiting child process #{pid}"
|
104
|
+
log.info "[#{incoming_message.delivery_tag}] Exiting child process #{pid}"
|
108
105
|
exit!(0)
|
109
106
|
end
|
110
107
|
|
@@ -120,24 +117,5 @@ module Leveret
|
|
120
117
|
job_klass = Object.const_get(payload[:job])
|
121
118
|
job_klass.perform(Leveret::Parameters.new(payload[:params]))
|
122
119
|
end
|
123
|
-
|
124
|
-
# Sends a message back to RabbitMQ confirming the completed execution of the message
|
125
|
-
#
|
126
|
-
# @param [Bunny::Channel] channel RabbitMQ channel to send the ack message on
|
127
|
-
# @param [String] delivery_tag The identifier that RabbitMQ uses to track the message. This will be used to ack
|
128
|
-
# or reject the message after processing.
|
129
|
-
# @param [Symbol] result :success, :reject or :requeue depending on how we want to acknowledge the message
|
130
|
-
def ack_message(channel, delivery_tag, result)
|
131
|
-
if result == :reject
|
132
|
-
log.debug "[#{delivery_tag}] Rejecting message"
|
133
|
-
channel.reject(delivery_tag)
|
134
|
-
elsif result == :requeue
|
135
|
-
log.debug "[#{delivery_tag}] Requeueing message"
|
136
|
-
channel.reject(delivery_tag, true)
|
137
|
-
else
|
138
|
-
log.debug "[#{delivery_tag}] Acknowledging message"
|
139
|
-
channel.acknowledge(delivery_tag)
|
140
|
-
end
|
141
|
-
end
|
142
120
|
end
|
143
121
|
end
|
data/lib/leveret.rb
CHANGED
@@ -3,10 +3,13 @@ require 'json'
|
|
3
3
|
require 'logger'
|
4
4
|
|
5
5
|
require 'leveret/configuration'
|
6
|
+
require 'leveret/delay_queue'
|
6
7
|
require 'leveret/job'
|
7
8
|
require 'leveret/log_formatter'
|
9
|
+
require 'leveret/message'
|
8
10
|
require 'leveret/parameters'
|
9
11
|
require 'leveret/queue'
|
12
|
+
require 'leveret/result_handler'
|
10
13
|
require 'leveret/worker'
|
11
14
|
require "leveret/version"
|
12
15
|
|
@@ -54,9 +57,14 @@ module Leveret
|
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
60
|
+
def delay_queue
|
61
|
+
@delay_queue ||= Leveret::DelayQueue.new
|
62
|
+
end
|
63
|
+
|
57
64
|
def reset_connection!
|
58
65
|
@mq_connection = nil
|
59
66
|
@channel = nil
|
67
|
+
@delay_queue = nil
|
60
68
|
end
|
61
69
|
|
62
70
|
# Logger used throughout Leveret, see {Configuration} for config options.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leveret
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Wentworth
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -101,10 +101,13 @@ files:
|
|
101
101
|
- lib/leveret.rb
|
102
102
|
- lib/leveret/cli.rb
|
103
103
|
- lib/leveret/configuration.rb
|
104
|
+
- lib/leveret/delay_queue.rb
|
104
105
|
- lib/leveret/job.rb
|
105
106
|
- lib/leveret/log_formatter.rb
|
107
|
+
- lib/leveret/message.rb
|
106
108
|
- lib/leveret/parameters.rb
|
107
109
|
- lib/leveret/queue.rb
|
110
|
+
- lib/leveret/result_handler.rb
|
108
111
|
- lib/leveret/version.rb
|
109
112
|
- lib/leveret/worker.rb
|
110
113
|
homepage: https://github.com/darkphnx/leveret
|