rabbit-wq 0.0.1 → 0.1.0
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/README.md +40 -2
- data/lib/rabbit_wq/command.rb +47 -0
- data/lib/rabbit_wq/logging.rb +2 -0
- data/lib/rabbit_wq/message_handler.rb +42 -1
- data/lib/rabbit_wq/queues.rb +27 -0
- data/lib/rabbit_wq/server.rb +11 -20
- data/lib/rabbit_wq/version.rb +1 -1
- data/lib/rabbit_wq/work.rb +21 -11
- data/lib/rabbit_wq/worker.rb +10 -0
- data/lib/rabbit_wq.rb +5 -2
- metadata +7 -4
data/README.md
CHANGED
@@ -22,7 +22,45 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
### Queueing Work
|
26
26
|
|
27
|
+
class SomeWorker < Struct.new( :some_variable )
|
28
|
+
def call
|
29
|
+
# do some work
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
worker = SomeWorker.new( 1 )
|
34
|
+
RabbitWQ::Work.enqueue( worker )
|
27
35
|
|
28
|
-
|
36
|
+
### Queueing Work in the Future
|
37
|
+
|
38
|
+
RabbitWQ::Work.enqueue( worker, delay: 30000 )
|
39
|
+
|
40
|
+
### Queueing Work with a Retry
|
41
|
+
|
42
|
+
RabbitWQ::Work.enqueue( worker, retry: 1 )
|
43
|
+
|
44
|
+
### Queueing Work with a Retry and a Retry Delay
|
45
|
+
|
46
|
+
RabbitWQ::Work.enqueue( worker, retry: 1, retry_delay: 30000 )
|
47
|
+
|
48
|
+
### Queueing Work with a Retry and Auto-Scaling Retry Dealy
|
49
|
+
|
50
|
+
RabbitWQ::Work.enqueue( worker, retry: 1, retry_delay: 'auto-scale' )
|
51
|
+
|
52
|
+
Auto-scale will set up retries at the following intervals: 1 min, 5 mins, 15 mins, 30 mins,
|
53
|
+
1 hr, 6 hrs, 12 hrs, 24 hrs. and 48 hrs.
|
54
|
+
|
55
|
+
### Using the Worker Module
|
56
|
+
|
57
|
+
class SomeWorker < Struct.new( :some_variable )
|
58
|
+
include RabbitWQ::Worker
|
59
|
+
|
60
|
+
def call
|
61
|
+
# do some work
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
worker = SomeWorker.new( 1 )
|
66
|
+
worker.work # same as RabbitWQ::Work.enqueue( worker )
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RabbitWQ
|
2
|
+
class Command
|
3
|
+
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize( args )
|
7
|
+
@options = {
|
8
|
+
:quiet => true,
|
9
|
+
:pid_dir => "#{Rails.root}/tmp/pids"
|
10
|
+
}
|
11
|
+
|
12
|
+
opts = OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart"
|
14
|
+
|
15
|
+
opts.on('-h', '--help', 'Show this message') do
|
16
|
+
puts opts
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
opts.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
20
|
+
@options[:pid_dir] = dir
|
21
|
+
end
|
22
|
+
opts.on('-i', '--interactive', 'Start the process in interactive mode.') do |n|
|
23
|
+
@options[:interactive] = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@args = opts.parse!( args )
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
if options[:interactive]
|
32
|
+
start_interactive( options )
|
33
|
+
else
|
34
|
+
start_daemonized( options )
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_interactive( options )
|
39
|
+
server = RabbitWQ::Server.new( options.merge( log: nil ))
|
40
|
+
server.start
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_daemonized( options )
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/rabbit_wq/logging.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'celluloid/autostart'
|
2
|
+
require 'yaml'
|
2
3
|
|
3
4
|
module RabbitWQ
|
4
5
|
class MessageHandler
|
5
6
|
|
6
7
|
include Celluloid
|
7
8
|
include Logging
|
9
|
+
include Queues
|
8
10
|
|
9
11
|
REQUEUE = true
|
10
12
|
|
@@ -12,18 +14,57 @@ module RabbitWQ
|
|
12
14
|
channel = options[:channel]
|
13
15
|
delivery_info = options[:delivery_info]
|
14
16
|
metadata = options[:metadata]
|
17
|
+
headers = metadata[:headers]
|
15
18
|
payload = options[:payload]
|
16
19
|
|
17
|
-
|
20
|
+
debug "PAYLOAD ARRIVED #{payload}"
|
21
|
+
|
22
|
+
$stdout.puts headers.inspect
|
23
|
+
worker = YAML::load( payload )
|
24
|
+
worker.call
|
18
25
|
channel.ack delivery_info.delivery_tag
|
26
|
+
rescue => e
|
27
|
+
debug e.message
|
28
|
+
handle_error( e, channel, delivery_info, payload, metadata )
|
19
29
|
end
|
20
30
|
|
21
31
|
protected
|
22
32
|
|
33
|
+
def handle_error( e, channel, delivery_info, payload, metadata )
|
34
|
+
headers = metadata[:headers]
|
35
|
+
|
36
|
+
if headers['retry']
|
37
|
+
retry_delay = headers.fetch( 'retry_delay', 30000 )
|
38
|
+
attempt = headers.fetch( 'attempt', 1 ).to_i
|
39
|
+
|
40
|
+
if retry_delay == 'auto-scale'
|
41
|
+
retry_delay = retry_delays( attempt )
|
42
|
+
end
|
43
|
+
|
44
|
+
Work.enqueue_payload( payload, headers.merge( delay: retry_delay, attempt: attempt + 1 ))
|
45
|
+
channel.nack delivery_info.delivery_tag
|
46
|
+
|
47
|
+
return
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
23
51
|
def requeue( channel, delivery_info, e=nil )
|
24
52
|
info ANSI.yellow { 'REQUEUE ' + e.message }
|
25
53
|
channel.reject delivery_info.delivery_tag, REQUEUE
|
26
54
|
end
|
27
55
|
|
56
|
+
def retry_delays( retry_num )
|
57
|
+
{
|
58
|
+
1 => 1,
|
59
|
+
2 => 5,
|
60
|
+
3 => 15,
|
61
|
+
4 => 30,
|
62
|
+
5 => 60,
|
63
|
+
6 => 360, # 6 hrs
|
64
|
+
7 => 720, # 12 hrs
|
65
|
+
8 => 1440, # 24 hrs
|
66
|
+
9 => 2880, # 48 hrs
|
67
|
+
}[retry_num] * 60000
|
68
|
+
end
|
28
69
|
end
|
29
70
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RabbitWQ
|
2
|
+
module Queues
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def mq
|
7
|
+
@mq ||= ::Bunny.new.tap do |bunny|
|
8
|
+
bunny.start
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def channel
|
13
|
+
@channel ||= mq.create_channel
|
14
|
+
end
|
15
|
+
|
16
|
+
#def work_exchange
|
17
|
+
#@work_exchange ||= channel.direct( WORK_EXCHANGE, durable: true )
|
18
|
+
#end
|
19
|
+
|
20
|
+
#def work_queue
|
21
|
+
#@work_queue ||= channel.queue( QUEUE,
|
22
|
+
#durable: true ).
|
23
|
+
#bind( work_exchange )
|
24
|
+
#end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/rabbit_wq/server.rb
CHANGED
@@ -3,11 +3,12 @@ require 'bunny'
|
|
3
3
|
module RabbitWQ
|
4
4
|
class Server
|
5
5
|
|
6
|
-
include ServerLogging
|
7
6
|
include Logging
|
7
|
+
include Queues
|
8
|
+
include ServerLogging
|
8
9
|
|
9
|
-
attr_reader :
|
10
|
-
:
|
10
|
+
attr_reader :options,
|
11
|
+
:work_consumer,
|
11
12
|
:work_exchange
|
12
13
|
|
13
14
|
def initialize( options )
|
@@ -29,28 +30,18 @@ module RabbitWQ
|
|
29
30
|
|
30
31
|
def finalize
|
31
32
|
info "SHUTTING DOWN"
|
32
|
-
|
33
|
+
work_consumer.cancel
|
33
34
|
mq.close
|
34
35
|
end
|
35
36
|
|
36
|
-
def mq
|
37
|
-
@mq ||= ::Bunny.new.tap do |bunny|
|
38
|
-
bunny.start
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def channel
|
43
|
-
@channel ||= mq.create_channel
|
44
|
-
end
|
45
|
-
|
46
37
|
def work_exchange
|
47
38
|
@work_exchange ||= channel.direct( WORK_EXCHANGE, durable: true )
|
48
39
|
end
|
49
40
|
|
50
|
-
def
|
51
|
-
@
|
52
|
-
|
53
|
-
|
41
|
+
def work_queue
|
42
|
+
@work_queue ||= channel.queue( QUEUE,
|
43
|
+
durable: true ).
|
44
|
+
bind( work_exchange )
|
54
45
|
end
|
55
46
|
|
56
47
|
def pool
|
@@ -58,8 +49,8 @@ module RabbitWQ
|
|
58
49
|
end
|
59
50
|
|
60
51
|
def run
|
61
|
-
@
|
62
|
-
|
52
|
+
@work_consumer = work_queue.subscribe( manual_ack: true ) do |delivery_info, metadata, payload|
|
53
|
+
info "LISTENER RECEIVED #{payload}"
|
63
54
|
|
64
55
|
pool.async.call( payload: payload,
|
65
56
|
delivery_info: delivery_info,
|
data/lib/rabbit_wq/version.rb
CHANGED
data/lib/rabbit_wq/work.rb
CHANGED
@@ -4,27 +4,37 @@ module RabbitWQ
|
|
4
4
|
module Work
|
5
5
|
|
6
6
|
def self.enqueue( worker, options={} )
|
7
|
-
|
7
|
+
payload = worker.to_yaml
|
8
|
+
enqueue_payload( payload, options )
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.enqueue_payload( payload, options={} )
|
12
|
+
delay = options.delete( :delay )
|
13
|
+
delay = nil if delay && delay < 5000
|
8
14
|
|
9
15
|
mq = ::Bunny.new.tap { |bunny| bunny.start }
|
10
16
|
channel = mq.create_channel
|
11
17
|
|
12
|
-
if
|
13
|
-
delay_x = channel.direct( "#{DELAY_EXCHANGE_PREFIX}-#{
|
14
|
-
work_x
|
15
|
-
queue = channel.queue( "#{DELAY_QUEUE_PREFIX}-#{options[:delay]}ms",
|
16
|
-
durable: true,
|
17
|
-
arguments: { "x-dead-letter-exchange" => work_x.name,
|
18
|
-
"x-message-ttl" => options[:delay] } ).
|
19
|
-
bind( delay_x )
|
18
|
+
if delay
|
19
|
+
delay_x = channel.direct( "#{DELAY_EXCHANGE_PREFIX}-#{delay}ms", durable: true )
|
20
|
+
work_x = channel.direct( WORK_EXCHANGE, durable: true )
|
20
21
|
|
21
|
-
|
22
|
+
channel.queue( "#{DELAY_QUEUE_PREFIX}-#{delay}ms",
|
23
|
+
durable: true,
|
24
|
+
arguments: { "x-dead-letter-exchange" => work_x.name,
|
25
|
+
"x-message-ttl" => delay } ).
|
26
|
+
bind( delay_x )
|
27
|
+
|
28
|
+
delay_x.publish( payload, durable: true,
|
29
|
+
headers: options )
|
22
30
|
return
|
23
31
|
end
|
24
32
|
|
25
33
|
work_q = channel.queue( QUEUE, durable: true )
|
26
|
-
work_q.publish(
|
34
|
+
work_q.publish( payload, durable: true,
|
35
|
+
headers: options )
|
27
36
|
end
|
28
37
|
|
38
|
+
|
29
39
|
end
|
30
40
|
end
|
data/lib/rabbit_wq.rb
CHANGED
@@ -4,20 +4,23 @@ module RabbitWQ
|
|
4
4
|
|
5
5
|
APP_ID = "rabbit-wq"
|
6
6
|
APP_NAME = "Rabbit Work Queue"
|
7
|
-
COMPANY = "Look Forward Enterprises"
|
8
7
|
DELAY_QUEUE_PREFIX = "work-delay" # TODO: Make this configurable (from ENV, or file?)
|
9
8
|
DELAY_EXCHANGE_PREFIX = "work-delay" # TODO: Make this configurable (from ENV, or file?)
|
9
|
+
ERROR_QUEUE = "work-error"
|
10
10
|
INT = "INT"
|
11
11
|
QUEUE = "work" # TODO: Make this configurable (from ENV, or file?)
|
12
|
-
VERSION_COPYRIGHT = "v#{VERSION} \u00A9#{Time.now.year}
|
12
|
+
VERSION_COPYRIGHT = "v#{VERSION} \u00A9#{Time.now.year}"
|
13
13
|
WORK_EXCHANGE = "work" # TODO: Make this configurable (from ENV, or file?)
|
14
14
|
|
15
|
+
autoload :Command, 'rabbit_wq/command'
|
15
16
|
autoload :Configuration, 'rabbit_wq/configuration'
|
16
17
|
autoload :Logging, 'rabbit_wq/logging'
|
18
|
+
autoload :Queues, 'rabbit_wq/queues'
|
17
19
|
autoload :MessageHandler, 'rabbit_wq/message_handler'
|
18
20
|
autoload :Server, 'rabbit_wq/server'
|
19
21
|
autoload :ServerLogging, 'rabbit_wq/server_logging'
|
20
22
|
autoload :Work, 'rabbit_wq/work'
|
23
|
+
autoload :Worker, 'rabbit_wq/worker'
|
21
24
|
|
22
25
|
def self.configuration
|
23
26
|
@configuration ||= Configuration.new
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabbit-wq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
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-11-
|
12
|
+
date: 2013-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -126,13 +126,16 @@ files:
|
|
126
126
|
- lib/rabbit-wq.rb
|
127
127
|
- lib/rabbit_wq.rb
|
128
128
|
- lib/rabbit_wq/cli.rb
|
129
|
+
- lib/rabbit_wq/command.rb
|
129
130
|
- lib/rabbit_wq/configuration.rb
|
130
131
|
- lib/rabbit_wq/logging.rb
|
131
132
|
- lib/rabbit_wq/message_handler.rb
|
133
|
+
- lib/rabbit_wq/queues.rb
|
132
134
|
- lib/rabbit_wq/server.rb
|
133
135
|
- lib/rabbit_wq/server_logging.rb
|
134
136
|
- lib/rabbit_wq/version.rb
|
135
137
|
- lib/rabbit_wq/work.rb
|
138
|
+
- lib/rabbit_wq/worker.rb
|
136
139
|
- rabbit-wq.gemspec
|
137
140
|
homepage: ''
|
138
141
|
licenses:
|
@@ -149,7 +152,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
152
|
version: '0'
|
150
153
|
segments:
|
151
154
|
- 0
|
152
|
-
hash:
|
155
|
+
hash: 2689177657118754314
|
153
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
157
|
none: false
|
155
158
|
requirements:
|
@@ -158,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
161
|
version: '0'
|
159
162
|
segments:
|
160
163
|
- 0
|
161
|
-
hash:
|
164
|
+
hash: 2689177657118754314
|
162
165
|
requirements: []
|
163
166
|
rubyforge_project:
|
164
167
|
rubygems_version: 1.8.25
|