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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ad5d86c738d44521e7c94d660a85cd7146c6934
4
- data.tar.gz: 4df57359cff0b01b6dd58d71b0b5a10962e605c6
3
+ metadata.gz: c09edf84323e1f658a68c1a63e642fbe6a24570e
4
+ data.tar.gz: d81b7f5a2637e8a3b6831db528a0aed27b17458e
5
5
  SHA512:
6
- metadata.gz: 4bcc8fe946aa915c5ab765a6b8cd99ead0e2254ccc88ac4852de110543d7afff7465674d5facf604c7addff393c52cea17c36b38869873778bce864d0264825e
7
- data.tar.gz: 1c2033a0b4b61b3147551744f67f4e8a09dfedd09d6f66c9204a9e816f209ed285cc13fa4a08c9c5bb157b4ca7ddaedeb7cf395059e89c19e12834f259579fc4
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 when a worker is
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
- queue.publish(payload, persistent: true, routing_key: name, priority: priority_id)
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 channel [Bunny::Channel] RabbitMQ channel receiver should use to send ack/reject
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, _properties, msg|
61
+ queue.subscribe(manual_ack: true) do |delivery_info, properties, msg|
64
62
  log.debug "Received #{msg} from #{name}"
65
- yield(queue.channel, delivery_info.delivery_tag, deserialize_payload(msg))
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
@@ -1,3 +1,3 @@
1
1
  module Leveret
2
- VERSION = "0.1.4".freeze
2
+ VERSION = "0.1.5".freeze
3
3
  end
@@ -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 |channel, delivery_tag, payload|
73
- fork_and_run(channel, delivery_tag, payload)
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 [Bunny::Channel] channel RabbitMQ channel to send the ack message on
92
- # @param [String] delivery_tag The identifier that RabbitMQ uses to track the message. This will be used to ack
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(payload)
104
- log.info "[#{delivery_tag}] Job returned #{result}"
105
- ack_message(channel, delivery_tag, result)
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
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-01 00:00:00.000000000 Z
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