leveret 0.1.4 → 0.1.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.
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