pebbles-river 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/pebbles/river/daemon_helper.rb +152 -0
- data/lib/pebbles/river/message.rb +14 -20
- data/lib/pebbles/river/multi_prefork.rb +49 -0
- data/lib/pebbles/river/river.rb +25 -27
- data/lib/pebbles/river/supervisor.rb +40 -9
- data/lib/pebbles/river/version.rb +1 -1
- data/lib/pebbles/river/worker.rb +6 -6
- data/lib/pebbles/river.rb +3 -0
- data/pebbles-river.gemspec +4 -3
- data/spec/lib/river_spec.rb +15 -16
- data/spec/lib/worker_spec.rb +36 -21
- data/spec/spec_helper.rb +17 -2
- metadata +30 -31
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODk5MzUyYzZiYTQzZGUzMGQyYTBmNDExZGE1YWRhNDA1N2RkYjVkZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTRkODBlMTZjMDMwNGI3MmM2MmE5MjdmZGY0ZTY5NzQxMDBhMGExZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzM5NzBhYTEzZTQ4ZjY0YTNmNzhmYTkxYTQ5MDNhMjEyMDk3ODRjNTM2M2Vk
|
10
|
+
MjcwMzA2ZWI4ZTM0YzgzNzA4NzNkNTk4ZjUwNzE4NDEwMjg2NGJiNDIwN2E0
|
11
|
+
YjlkYTdiOTQzODgxYjU0OTdjNzI3YTFjNjcxNDdlZTA4YjlhMWQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZDhhOGZlZTIyODk3NWI5MmNhMDgyNjk2MjE4ZjNkNjNjMTQ4MDA0NWFmY2Qx
|
14
|
+
YmNmYmZkNTVjODk5NmUyYjljN2U1MjZmNDY5MDU4NzIyNDMzMGMyNTVmYWQy
|
15
|
+
ZjJlZTY3MmYxNTU4Yjc3NGVkMGQ5NTc0M2FmYjM0YzQwZjk2NzU=
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'mercenary'
|
3
|
+
|
4
|
+
module Pebbles
|
5
|
+
module River
|
6
|
+
|
7
|
+
# Simple helper class for easily writing daemons that run multiple queue
|
8
|
+
# workers. Handles command line parsing and daemonization.
|
9
|
+
#
|
10
|
+
# Adapter should support:
|
11
|
+
#
|
12
|
+
# * `name`: Name of the process. Optional, defaults to `$0`.
|
13
|
+
#
|
14
|
+
# * `configure_start_command(command)`. Implement to add more options to
|
15
|
+
# the start command, eg. configuration options. Optional.
|
16
|
+
#
|
17
|
+
# * `on_start(options)`. Implement to inject code before the program
|
18
|
+
# starts. Options are the start options, a hash. Optional.
|
19
|
+
#
|
20
|
+
# * `configure_supervisor(supervisor)`. This must call `add_listener` on the
|
21
|
+
# supervisor to add workers. Required.
|
22
|
+
#
|
23
|
+
class DaemonHelper
|
24
|
+
|
25
|
+
def initialize(adapter, options = {})
|
26
|
+
@adapter = adapter
|
27
|
+
|
28
|
+
@name = @adapter.name if @adapter.respond_to?(:name)
|
29
|
+
@name ||= File.basename($0).gsub(/\.rb$/, '')
|
30
|
+
|
31
|
+
@logger = options.fetch(:logger, Logger.new($stderr))
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
Mercenary.program(@name) do |p|
|
36
|
+
p.syntax "#{@name} <subcommand> [OPTION ...]"
|
37
|
+
p.command(:start) do |c|
|
38
|
+
c.syntax 'start'
|
39
|
+
c.description 'Starts daemon'
|
40
|
+
c.option :daemonize, '-d', '--daemon', 'To daemonize; otherwise will run synchronously.'
|
41
|
+
c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
|
42
|
+
c.option :workers, Integer, '-w', '--workers N', 'Set number of workers per queue (defaults to 1).'
|
43
|
+
c.action do |_, options|
|
44
|
+
start(options)
|
45
|
+
end
|
46
|
+
if @adapter.respond_to?(:configure_start_command)
|
47
|
+
@adapter.configure_start_command(c)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
p.command(:stop) do |c|
|
51
|
+
c.syntax 'stop'
|
52
|
+
c.description 'Stops daemon'
|
53
|
+
c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
|
54
|
+
c.action do |_, options|
|
55
|
+
stop(options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
p.command(:status) do |c|
|
59
|
+
c.syntax 'status'
|
60
|
+
c.description 'Prints daemon status'
|
61
|
+
c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
|
62
|
+
c.action do |_, options|
|
63
|
+
status(options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if ARGV.any?
|
69
|
+
abort "Unknown command '#{ARGV.first}'."
|
70
|
+
else
|
71
|
+
abort "Run with -h for help."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def start(options)
|
78
|
+
if @adapter.respond_to?(:on_start)
|
79
|
+
@adapter.on_start(options)
|
80
|
+
end
|
81
|
+
|
82
|
+
daemon = new_daemon(options)
|
83
|
+
if daemon.alive?
|
84
|
+
abort "#{daemon.name} is already running."
|
85
|
+
end
|
86
|
+
if options[:daemonize]
|
87
|
+
print "Starting #{daemon.name}"
|
88
|
+
daemon.startup
|
89
|
+
puts ", done."
|
90
|
+
else
|
91
|
+
daemon.server.startup
|
92
|
+
end
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
def stop(options)
|
97
|
+
daemon = get_daemon(options)
|
98
|
+
unless daemon.alive?
|
99
|
+
abort "#{daemon.name} is not running."
|
100
|
+
end
|
101
|
+
print "Stopping #{daemon.name}"
|
102
|
+
daemon.shutdown
|
103
|
+
puts ", done."
|
104
|
+
exit
|
105
|
+
end
|
106
|
+
|
107
|
+
def status(options)
|
108
|
+
daemon = get_daemon(options)
|
109
|
+
if daemon.alive?
|
110
|
+
puts "#{daemon.name} is running."
|
111
|
+
else
|
112
|
+
puts "#{daemon.name} is not running."
|
113
|
+
end
|
114
|
+
exit(daemon.alive? ? 0 : 1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_daemon(options)
|
118
|
+
unless options[:pidfile]
|
119
|
+
abort "Specify pidfile with --pidfile."
|
120
|
+
end
|
121
|
+
|
122
|
+
Servolux::Daemon.new(
|
123
|
+
name: @name,
|
124
|
+
pid_file: options[:pidfile],
|
125
|
+
logger: @logger,
|
126
|
+
startup_command: '/bin/true',
|
127
|
+
nochdir: true)
|
128
|
+
end
|
129
|
+
|
130
|
+
def new_daemon(options)
|
131
|
+
unless options[:pidfile]
|
132
|
+
abort "Specify pidfile with --pidfile."
|
133
|
+
end
|
134
|
+
|
135
|
+
supervisor = Pebbles::River::Supervisor.new(@name,
|
136
|
+
pid_file: options[:pidfile],
|
137
|
+
logger: @logger,
|
138
|
+
worker_count: options[:workers])
|
139
|
+
|
140
|
+
@adapter.configure_supervisor(supervisor)
|
141
|
+
|
142
|
+
supervisor.start_workers
|
143
|
+
|
144
|
+
Servolux::Daemon.new(
|
145
|
+
server: supervisor,
|
146
|
+
nochdir: true)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
@@ -3,11 +3,11 @@ module Pebbles
|
|
3
3
|
|
4
4
|
class InvalidPayloadError < StandardError
|
5
5
|
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :content
|
7
7
|
|
8
|
-
def
|
8
|
+
def initialize(message, content)
|
9
9
|
super(message)
|
10
|
-
@
|
10
|
+
@content = content
|
11
11
|
end
|
12
12
|
|
13
13
|
end
|
@@ -16,22 +16,22 @@ module Pebbles
|
|
16
16
|
|
17
17
|
attr_reader :payload
|
18
18
|
attr_reader :queue
|
19
|
-
attr_reader :
|
19
|
+
attr_reader :delivery_info
|
20
20
|
|
21
|
-
def self.deserialize_payload(
|
22
|
-
if
|
21
|
+
def self.deserialize_payload(content)
|
22
|
+
if content
|
23
23
|
begin
|
24
|
-
return JSON.parse(
|
24
|
+
return JSON.parse(content)
|
25
25
|
rescue => e
|
26
|
-
raise InvalidPayloadError.new(e.message,
|
26
|
+
raise InvalidPayloadError.new(e.message, content)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def initialize(
|
31
|
+
def initialize(content, delivery_info, queue = nil)
|
32
32
|
@queue = queue
|
33
|
-
@
|
34
|
-
@payload = self.class.deserialize_payload(
|
33
|
+
@delivery_info = delivery_info
|
34
|
+
@payload = self.class.deserialize_payload(content)
|
35
35
|
end
|
36
36
|
|
37
37
|
def ==(other)
|
@@ -41,21 +41,15 @@ module Pebbles
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def delivery_tag
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
def delivery_details
|
48
|
-
@raw_message[:delivery_details] || {}
|
44
|
+
@delivery_info.delivery_tag if @delivery_info
|
49
45
|
end
|
50
46
|
|
51
47
|
def ack
|
52
|
-
@queue.ack(delivery_tag
|
48
|
+
@queue.channel.ack(delivery_tag)
|
53
49
|
end
|
54
50
|
|
55
51
|
def nack
|
56
|
-
|
57
|
-
# let messages simply expire, since pre-0.9 doesn't have a way to nack.
|
58
|
-
#@channel.nack(delivery_tag, false)
|
52
|
+
@queue.channel.nack(delivery_tag, false, true)
|
59
53
|
end
|
60
54
|
|
61
55
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pebbles
|
2
|
+
module River
|
3
|
+
|
4
|
+
# Overrides preforker to instantiate a round-robin set of modules. The
|
5
|
+
# desired worker count is multiplied by the number of modules; for example,
|
6
|
+
# if specifying modules [A, B] and the worker count is 2, then 4 actual
|
7
|
+
# processes will be forked.
|
8
|
+
class MultiPrefork < ::Servolux::Prefork
|
9
|
+
|
10
|
+
def initialize(options, &block)
|
11
|
+
raise ArgumentError, "Block invocation not supported" if block
|
12
|
+
raise ArgumentError, "Must pass :modules, not :module" if options[:module]
|
13
|
+
|
14
|
+
# Like Prefork we support passing procs
|
15
|
+
@modules = options[:modules].map { |m|
|
16
|
+
if m.is_a?(Proc)
|
17
|
+
Module.new { define_method :execute, &m }
|
18
|
+
else
|
19
|
+
m
|
20
|
+
end
|
21
|
+
}
|
22
|
+
@index = 0
|
23
|
+
|
24
|
+
options = {}.merge(options)
|
25
|
+
options[:module] = @modules.first
|
26
|
+
options.delete(:modules)
|
27
|
+
if (min_workers = options[:min_workers])
|
28
|
+
options[:min_workers] = min_workers * @modules.length
|
29
|
+
end
|
30
|
+
if (max_workers = options[:max_workers])
|
31
|
+
options[:max_workers] = max_workers * @modules.length
|
32
|
+
end
|
33
|
+
|
34
|
+
super(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# This cheats by overriding `Prefork#add_workers` and replacing the
|
38
|
+
# `@module instance` variable.
|
39
|
+
def add_workers(number = 1)
|
40
|
+
number.times do
|
41
|
+
@module = @modules[@index]
|
42
|
+
super(1)
|
43
|
+
@index = (@index + 1) % @modules.length
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/pebbles/river/river.rb
CHANGED
@@ -4,6 +4,9 @@ module Pebbles
|
|
4
4
|
class River
|
5
5
|
|
6
6
|
attr_reader :environment
|
7
|
+
attr_reader :exchange
|
8
|
+
attr_reader :session
|
9
|
+
attr_reader :channel
|
7
10
|
|
8
11
|
def initialize(options = {})
|
9
12
|
options = {environment: options} if options.is_a?(String) # Backwards compatibility
|
@@ -13,36 +16,41 @@ module Pebbles
|
|
13
16
|
end
|
14
17
|
|
15
18
|
def connected?
|
16
|
-
|
19
|
+
@session && @session.connected?
|
17
20
|
end
|
18
21
|
|
19
22
|
def connect
|
20
|
-
unless
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
unless @session
|
24
|
+
handle_session_error do
|
25
|
+
@session = Bunny::Session.new
|
26
|
+
@session.start
|
27
|
+
|
28
|
+
@channel = @session.create_channel
|
29
|
+
|
30
|
+
@exchange = @channel.exchange(exchange_name, EXCHANGE_OPTIONS.dup)
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
27
34
|
|
28
35
|
def disconnect
|
29
|
-
|
30
|
-
if
|
36
|
+
@exchange = nil
|
37
|
+
if @channel
|
38
|
+
@channel.close
|
39
|
+
@channel = nil
|
40
|
+
end
|
41
|
+
if @session
|
31
42
|
begin
|
32
|
-
|
43
|
+
@session.stop
|
33
44
|
rescue *CONNECTION_EXCEPTIONS
|
34
45
|
# Ignore
|
35
46
|
end
|
47
|
+
@session = nil
|
36
48
|
end
|
37
|
-
|
38
|
-
# This will force fresh connection the next time we need it, otherwise
|
39
|
-
# Bunny won't create queues that no longer might exist
|
40
|
-
@bunny = nil
|
41
49
|
end
|
42
50
|
|
43
51
|
def publish(options = {})
|
44
52
|
connect
|
45
|
-
|
53
|
+
handle_session_error(SendFailure) do
|
46
54
|
exchange.publish(options.to_json,
|
47
55
|
persistent: options.fetch(:persistent, true),
|
48
56
|
key: Routing.routing_key_for(options.slice(:event, :uid)))
|
@@ -53,22 +61,17 @@ module Pebbles
|
|
53
61
|
raise ArgumentError.new 'Queue must be named' unless options[:name]
|
54
62
|
|
55
63
|
connect
|
56
|
-
|
57
|
-
queue = bunny.queue(options[:name], QUEUE_OPTIONS.dup)
|
64
|
+
queue = @channel.queue(options[:name], QUEUE_OPTIONS.dup)
|
58
65
|
Subscription.new(options).queries.each do |key|
|
59
66
|
queue.bind(exchange.name, key: key)
|
60
67
|
end
|
61
68
|
queue
|
62
69
|
end
|
63
70
|
|
64
|
-
def exchange_name
|
65
|
-
return @exchange_name ||= format_exchange_name
|
66
|
-
end
|
67
|
-
|
68
71
|
private
|
69
72
|
|
70
|
-
def
|
71
|
-
@
|
73
|
+
def exchange_name
|
74
|
+
return @exchange_name ||= format_exchange_name
|
72
75
|
end
|
73
76
|
|
74
77
|
def format_exchange_name
|
@@ -77,12 +80,7 @@ module Pebbles
|
|
77
80
|
name
|
78
81
|
end
|
79
82
|
|
80
|
-
def
|
81
|
-
connect
|
82
|
-
@exchange ||= bunny.exchange(exchange_name, EXCHANGE_OPTIONS.dup)
|
83
|
-
end
|
84
|
-
|
85
|
-
def handle_connection_error(exception_klass = ConnectFailure, &block)
|
83
|
+
def handle_session_error(exception_klass = ConnectFailure, &block)
|
86
84
|
last_exception = nil
|
87
85
|
Timeout.timeout(MAX_RETRY_TIMEOUT) do
|
88
86
|
retry_until, retry_count = nil, 0
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Pebbles
|
2
2
|
module River
|
3
3
|
|
4
|
+
class ConfigurationError < StandardError; end
|
5
|
+
|
4
6
|
class Supervisor < Servolux::Server
|
5
7
|
|
6
8
|
def initialize(name, options = {})
|
@@ -9,33 +11,62 @@ module Pebbles
|
|
9
11
|
options.assert_valid_keys(:logger, :pid_file, :worker_count, :worker)
|
10
12
|
|
11
13
|
@worker_count = options[:worker_count] || 1
|
12
|
-
@worker = options[:worker]
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
@queue_modules = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def start_workers
|
19
|
+
if @queue_modules.empty?
|
20
|
+
raise ConfigurationError.new("No listeners configured")
|
21
|
+
end
|
22
|
+
|
23
|
+
@prefork = MultiPrefork.new(
|
24
|
+
min_workers: @worker_count,
|
25
|
+
modules: @queue_modules)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_listener(listener, queue_spec)
|
29
|
+
worker = Pebbles::River::Worker.new(listener,
|
30
|
+
queue: queue_spec,
|
31
|
+
on_exception: ->(e) {
|
32
|
+
if logger.respond_to?(:exception)
|
33
|
+
logger.exception(e)
|
34
|
+
else
|
35
|
+
logger.error("Exception #{e.class}: #{e} #{e.backtrace.join("\n")}")
|
36
|
+
end
|
37
|
+
})
|
38
|
+
|
39
|
+
process_name = "#{@name}: queue worker: #{queue_spec[:name]}"
|
40
|
+
logger = @logger
|
41
|
+
|
42
|
+
@queue_modules.push(-> {
|
43
|
+
$0 = process_name
|
17
44
|
trap('TERM') { worker.stop }
|
18
45
|
worker.run
|
19
|
-
|
46
|
+
})
|
20
47
|
end
|
21
48
|
|
49
|
+
# From Servolux::Server
|
22
50
|
def before_starting
|
23
51
|
$0 = "#{name}: master"
|
24
52
|
|
25
53
|
logger.info "Starting workers"
|
26
|
-
@
|
54
|
+
@prefork.start(1)
|
27
55
|
end
|
28
56
|
|
57
|
+
# From Servolux::Server
|
29
58
|
def after_stopping
|
30
59
|
shutdown_workers
|
31
60
|
end
|
32
61
|
|
62
|
+
# From Servolux::Server
|
33
63
|
def usr2
|
34
64
|
shutdown_workers
|
35
65
|
end
|
36
66
|
|
67
|
+
# From Servolux::Server
|
37
68
|
def run
|
38
|
-
@
|
69
|
+
@prefork.ensure_worker_pool_size
|
39
70
|
rescue => e
|
40
71
|
if logger.respond_to? :exception
|
41
72
|
logger.exception(e)
|
@@ -49,9 +80,9 @@ module Pebbles
|
|
49
80
|
|
50
81
|
def shutdown_workers
|
51
82
|
logger.info "Shutting down all workers"
|
52
|
-
@
|
83
|
+
@prefork.stop
|
53
84
|
loop do
|
54
|
-
break if @
|
85
|
+
break if @prefork.live_worker_count <= 0
|
55
86
|
logger.info "Waiting for workers to quit"
|
56
87
|
sleep 0.25
|
57
88
|
end
|
data/lib/pebbles/river/worker.rb
CHANGED
@@ -115,9 +115,9 @@ module Pebbles
|
|
115
115
|
|
116
116
|
def process_next
|
117
117
|
with_exceptions do
|
118
|
-
queue.pop(
|
119
|
-
if
|
120
|
-
process_message(
|
118
|
+
queue.pop(ack: true) do |delivery_info, properties, content|
|
119
|
+
if delivery_info
|
120
|
+
process_message(delivery_info, properties, content)
|
121
121
|
return true
|
122
122
|
else
|
123
123
|
return false
|
@@ -126,12 +126,12 @@ module Pebbles
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
def process_message(
|
129
|
+
def process_message(delivery_info, properties, content)
|
130
130
|
begin
|
131
|
-
message = Message.new(
|
131
|
+
message = Message.new(content, delivery_info, queue)
|
132
132
|
rescue => exception
|
133
133
|
ignore_exceptions do
|
134
|
-
queue.nack(
|
134
|
+
queue.channel.nack(delivery_info, false, true)
|
135
135
|
end
|
136
136
|
raise exception
|
137
137
|
else
|
data/lib/pebbles/river.rb
CHANGED
@@ -8,6 +8,7 @@ require 'servolux'
|
|
8
8
|
require_relative "river/version"
|
9
9
|
require_relative "river/errors"
|
10
10
|
require_relative "river/message"
|
11
|
+
require_relative "river/multi_prefork"
|
11
12
|
require_relative "river/worker"
|
12
13
|
require_relative "river/subscription"
|
13
14
|
require_relative "river/supervisor"
|
@@ -15,3 +16,5 @@ require_relative "river/routing"
|
|
15
16
|
require_relative "river/river"
|
16
17
|
require_relative "river/compatibility"
|
17
18
|
require_relative "river/rate_limiter"
|
19
|
+
require_relative "river/multi_prefork"
|
20
|
+
require_relative "river/daemon_helper"
|
data/pebbles-river.gemspec
CHANGED
@@ -20,12 +20,13 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_runtime_dependency 'pebblebed', '>= 0.
|
24
|
-
spec.add_runtime_dependency 'bunny', '~>
|
23
|
+
spec.add_runtime_dependency 'pebblebed', '>= 0.2'
|
24
|
+
spec.add_runtime_dependency 'bunny', '~> 1.2.2'
|
25
25
|
spec.add_runtime_dependency 'activesupport', '>= 3.0'
|
26
26
|
spec.add_runtime_dependency 'servolux', '~> 0.10'
|
27
|
+
spec.add_runtime_dependency 'mercenary', '~> 0.3.3'
|
27
28
|
|
28
|
-
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'rspec', "~> 2.99"
|
29
30
|
spec.add_development_dependency "bundler", "~> 1.5"
|
30
31
|
spec.add_development_dependency "rake"
|
31
32
|
spec.add_development_dependency "simplecov"
|
data/spec/lib/river_spec.rb
CHANGED
@@ -21,11 +21,13 @@ describe Pebbles::River::River do
|
|
21
21
|
]
|
22
22
|
|
23
23
|
after(:each) do
|
24
|
-
subject.
|
25
|
-
queue
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
if (channel = subject.channel)
|
25
|
+
channel.queues.each do |name, queue|
|
26
|
+
queue.purge
|
27
|
+
# If you don't delete the queue, the subscription will not
|
28
|
+
# change, even if you give it a new one.
|
29
|
+
queue.delete
|
30
|
+
end
|
29
31
|
end
|
30
32
|
subject.disconnect
|
31
33
|
end
|
@@ -33,14 +35,16 @@ describe Pebbles::River::River do
|
|
33
35
|
it { subject.should_not be_connected }
|
34
36
|
|
35
37
|
it "gets the name right" do
|
36
|
-
subject.
|
38
|
+
subject.connect
|
39
|
+
subject.exchange.name.should eq('pebblebed.river.whatever')
|
37
40
|
end
|
38
41
|
|
39
42
|
context "in production" do
|
40
43
|
subject { Pebbles::River::River.new('production') }
|
41
44
|
|
42
45
|
it "doesn't append the thing" do
|
43
|
-
subject.
|
46
|
+
subject.connect
|
47
|
+
subject.exchange.name.should eq('pebblebed.river')
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
@@ -62,12 +66,6 @@ describe Pebbles::River::River do
|
|
62
66
|
subject.should be_connected
|
63
67
|
end
|
64
68
|
|
65
|
-
it "connects if you try to talk to the exchange" do
|
66
|
-
subject.should_not be_connected
|
67
|
-
subject.send(:exchange)
|
68
|
-
subject.should be_connected
|
69
|
-
end
|
70
|
-
|
71
69
|
describe "publishing" do
|
72
70
|
|
73
71
|
it "gets selected messages" do
|
@@ -96,7 +94,8 @@ describe Pebbles::River::River do
|
|
96
94
|
queue = subject.queue(:name => 'eatseverything')
|
97
95
|
subject.publish(:event => 'smile', :source => 'rspec', :uid => 'klass:path$1', :attributes => {:a => 'b'})
|
98
96
|
sleep(0.1)
|
99
|
-
|
97
|
+
_, _, payload = queue.pop
|
98
|
+
JSON.parse(payload)['uid'].should eq('klass:path$1')
|
100
99
|
end
|
101
100
|
|
102
101
|
CONNECTION_EXCEPTIONS.each do |exception_class|
|
@@ -108,7 +107,7 @@ describe Pebbles::River::River do
|
|
108
107
|
exchange.stub(:publish) do
|
109
108
|
count += 1
|
110
109
|
if count < 3
|
111
|
-
raise exception_class
|
110
|
+
raise create_exception(exception_class)
|
112
111
|
end
|
113
112
|
end
|
114
113
|
|
@@ -133,7 +132,7 @@ describe Pebbles::River::River do
|
|
133
132
|
it "retries with exponential backoff until timeout and gives up with SendFailure" do
|
134
133
|
exchange = double('exchange')
|
135
134
|
exchange.stub(:publish) do
|
136
|
-
raise exception_class
|
135
|
+
raise create_exception(exception_class)
|
137
136
|
end
|
138
137
|
subject.stub(:exchange) { exchange }
|
139
138
|
|
data/spec/lib/worker_spec.rb
CHANGED
@@ -42,27 +42,37 @@ describe Worker do
|
|
42
42
|
{'answer' => 42}
|
43
43
|
end
|
44
44
|
|
45
|
+
let :delivery_info do
|
46
|
+
Class.new {
|
47
|
+
def delivery_tag
|
48
|
+
'doink'
|
49
|
+
end
|
50
|
+
}.new
|
51
|
+
end
|
52
|
+
|
45
53
|
let :raw_message do
|
46
|
-
{
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
[delivery_info, {}, JSON.dump(payload)]
|
55
|
+
end
|
56
|
+
|
57
|
+
let :channel do
|
58
|
+
channel = double('Bunny::Channel')
|
59
|
+
channel.stub(:ack) { }
|
60
|
+
channel.stub(:nack) { }
|
61
|
+
channel
|
62
|
+
end
|
52
63
|
|
53
64
|
let :queue do
|
54
65
|
queue = double('Bunny::Queue')
|
55
66
|
queue.stub(:close) { nil }
|
56
67
|
queue.stub(:pop) { |&block|
|
57
|
-
block.call(raw_message)
|
68
|
+
block.call(*raw_message)
|
58
69
|
}
|
59
|
-
queue.stub(:
|
60
|
-
queue.stub(:nack) { }
|
70
|
+
queue.stub(:channel) { channel }
|
61
71
|
queue
|
62
72
|
end
|
63
73
|
|
64
74
|
let :message do
|
65
|
-
Message.new(raw_message, queue)
|
75
|
+
Message.new(raw_message[2], nil, queue)
|
66
76
|
end
|
67
77
|
|
68
78
|
let :river do
|
@@ -99,8 +109,9 @@ describe Worker do
|
|
99
109
|
|
100
110
|
it 'creates a queue and runs worker with it' do
|
101
111
|
expect(queue).to receive(:pop).at_least(1).times
|
102
|
-
|
103
|
-
expect(
|
112
|
+
|
113
|
+
expect(channel).to receive(:ack).at_least(1).times
|
114
|
+
expect(channel).to_not receive(:nack)
|
104
115
|
|
105
116
|
expect(null_handler).to receive(:call).with(message)
|
106
117
|
|
@@ -114,7 +125,7 @@ describe Worker do
|
|
114
125
|
context 'when queue is empty' do
|
115
126
|
it 'does nothing' do
|
116
127
|
queue.stub(:pop) { |&block|
|
117
|
-
block.call(
|
128
|
+
block.call(nil, nil, nil)
|
118
129
|
}
|
119
130
|
|
120
131
|
expect(queue).to receive(:pop).at_least(1).times
|
@@ -128,7 +139,7 @@ describe Worker do
|
|
128
139
|
|
129
140
|
it 'calls #on_idle if implemented' do
|
130
141
|
queue.stub(:pop) { |&block|
|
131
|
-
block.call(
|
142
|
+
block.call(nil, nil, nil)
|
132
143
|
}
|
133
144
|
|
134
145
|
null_handler.stub(:on_idle) { }
|
@@ -140,10 +151,11 @@ describe Worker do
|
|
140
151
|
|
141
152
|
context 'when handler is successful' do
|
142
153
|
it 'acks the message' do
|
143
|
-
expect(queue).to receive(:ack).at_least(1).times
|
144
|
-
expect(queue).to_not receive(:nack)
|
145
154
|
expect(queue).to_not receive(:close)
|
146
155
|
|
156
|
+
expect(channel).to receive(:ack).at_least(1).times
|
157
|
+
expect(channel).to_not receive(:nack)
|
158
|
+
|
147
159
|
expect(river).to receive(:connected?).with(no_args).at_least(1).times
|
148
160
|
expect(river).to_not receive(:connect)
|
149
161
|
|
@@ -154,10 +166,11 @@ describe Worker do
|
|
154
166
|
context 'when handler returns false' do
|
155
167
|
if false # Needs Bunny 0.9+
|
156
168
|
it 'nacks the message' do
|
157
|
-
expect(queue).to receive(:nack).at_least(1).times
|
158
|
-
expect(queue).to_not receive(:ack)
|
159
169
|
expect(queue).to_not receive(:close)
|
160
170
|
|
171
|
+
expect(channel).to receive(:nack).at_least(1).times
|
172
|
+
expect(channel).to_not receive(:ack)
|
173
|
+
|
161
174
|
expect(river).to receive(:connected?).with(no_args).at_least(1).times
|
162
175
|
expect(river).to_not receive(:connect)
|
163
176
|
|
@@ -210,7 +223,7 @@ describe Worker do
|
|
210
223
|
].each do |exception_class|
|
211
224
|
context "connection exception #{exception_class}" do
|
212
225
|
let :exception do
|
213
|
-
exception_class
|
226
|
+
create_exception(exception_class)
|
214
227
|
end
|
215
228
|
|
216
229
|
let :handler do
|
@@ -257,8 +270,10 @@ describe Worker do
|
|
257
270
|
|
258
271
|
logger = double('logger')
|
259
272
|
logger.stub(:error) { }
|
273
|
+
|
274
|
+
# TODO: Test exception contents here
|
260
275
|
expect(logger).to receive(:error).
|
261
|
-
with(
|
276
|
+
with(/.*/).at_least(1).times
|
262
277
|
|
263
278
|
river.stub(:disconnect)
|
264
279
|
|
@@ -319,7 +334,7 @@ describe Worker do
|
|
319
334
|
context 'when queue is empty' do
|
320
335
|
it 'calls #sleep to delay polling a bit' do
|
321
336
|
queue.stub(:pop) { |&block|
|
322
|
-
block.call(
|
337
|
+
block.call(nil, nil, nil)
|
323
338
|
}
|
324
339
|
|
325
340
|
count = 0
|
data/spec/spec_helper.rb
CHANGED
@@ -8,6 +8,21 @@ SimpleCov.start
|
|
8
8
|
|
9
9
|
require_relative '../lib/pebbles/river'
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
module SpecHelpers
|
12
|
+
def create_exception(exception_class)
|
13
|
+
# TODO: Using #allocate here because Bunny has a whole bunch
|
14
|
+
# of exceptions; the one comparison against ECONNRESET is sad
|
15
|
+
# and could be generalized
|
16
|
+
if exception_class == Errno::ECONNRESET
|
17
|
+
raise exception_class.new
|
18
|
+
else
|
19
|
+
raise exception_class.allocate
|
20
|
+
end
|
21
|
+
end
|
13
22
|
end
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
config.mock_with :rspec
|
26
|
+
config.include SpecHelpers
|
27
|
+
end
|
28
|
+
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pebbles-river
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Alexander Staubo
|
@@ -10,44 +9,39 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2014-06-
|
12
|
+
date: 2014-06-23 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: pebblebed
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ! '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.
|
20
|
+
version: '0.2'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
25
|
- - ! '>='
|
29
26
|
- !ruby/object:Gem::Version
|
30
|
-
version: 0.
|
27
|
+
version: '0.2'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: bunny
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
32
|
- - ~>
|
37
33
|
- !ruby/object:Gem::Version
|
38
|
-
version:
|
34
|
+
version: 1.2.2
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
39
|
- - ~>
|
45
40
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
41
|
+
version: 1.2.2
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: activesupport
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
46
|
- - ! '>='
|
53
47
|
- !ruby/object:Gem::Version
|
@@ -55,7 +49,6 @@ dependencies:
|
|
55
49
|
type: :runtime
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
53
|
- - ! '>='
|
61
54
|
- !ruby/object:Gem::Version
|
@@ -63,7 +56,6 @@ dependencies:
|
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: servolux
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
60
|
- - ~>
|
69
61
|
- !ruby/object:Gem::Version
|
@@ -71,31 +63,41 @@ dependencies:
|
|
71
63
|
type: :runtime
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
67
|
- - ~>
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: '0.10'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: mercenary
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.3.3
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 0.3.3
|
79
84
|
- !ruby/object:Gem::Dependency
|
80
85
|
name: rspec
|
81
86
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
87
|
requirements:
|
84
|
-
- -
|
88
|
+
- - ~>
|
85
89
|
- !ruby/object:Gem::Version
|
86
|
-
version: '
|
90
|
+
version: '2.99'
|
87
91
|
type: :development
|
88
92
|
prerelease: false
|
89
93
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
94
|
requirements:
|
92
|
-
- -
|
95
|
+
- - ~>
|
93
96
|
- !ruby/object:Gem::Version
|
94
|
-
version: '
|
97
|
+
version: '2.99'
|
95
98
|
- !ruby/object:Gem::Dependency
|
96
99
|
name: bundler
|
97
100
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
101
|
requirements:
|
100
102
|
- - ~>
|
101
103
|
- !ruby/object:Gem::Version
|
@@ -103,7 +105,6 @@ dependencies:
|
|
103
105
|
type: :development
|
104
106
|
prerelease: false
|
105
107
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
108
|
requirements:
|
108
109
|
- - ~>
|
109
110
|
- !ruby/object:Gem::Version
|
@@ -111,7 +112,6 @@ dependencies:
|
|
111
112
|
- !ruby/object:Gem::Dependency
|
112
113
|
name: rake
|
113
114
|
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
115
|
requirements:
|
116
116
|
- - ! '>='
|
117
117
|
- !ruby/object:Gem::Version
|
@@ -119,7 +119,6 @@ dependencies:
|
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
122
|
requirements:
|
124
123
|
- - ! '>='
|
125
124
|
- !ruby/object:Gem::Version
|
@@ -127,7 +126,6 @@ dependencies:
|
|
127
126
|
- !ruby/object:Gem::Dependency
|
128
127
|
name: simplecov
|
129
128
|
requirement: !ruby/object:Gem::Requirement
|
130
|
-
none: false
|
131
129
|
requirements:
|
132
130
|
- - ! '>='
|
133
131
|
- !ruby/object:Gem::Version
|
@@ -135,7 +133,6 @@ dependencies:
|
|
135
133
|
type: :development
|
136
134
|
prerelease: false
|
137
135
|
version_requirements: !ruby/object:Gem::Requirement
|
138
|
-
none: false
|
139
136
|
requirements:
|
140
137
|
- - ! '>='
|
141
138
|
- !ruby/object:Gem::Version
|
@@ -154,8 +151,10 @@ files:
|
|
154
151
|
- Rakefile
|
155
152
|
- lib/pebbles/river.rb
|
156
153
|
- lib/pebbles/river/compatibility.rb
|
154
|
+
- lib/pebbles/river/daemon_helper.rb
|
157
155
|
- lib/pebbles/river/errors.rb
|
158
156
|
- lib/pebbles/river/message.rb
|
157
|
+
- lib/pebbles/river/multi_prefork.rb
|
159
158
|
- lib/pebbles/river/rate_limiter.rb
|
160
159
|
- lib/pebbles/river/river.rb
|
161
160
|
- lib/pebbles/river/routing.rb
|
@@ -172,27 +171,26 @@ files:
|
|
172
171
|
homepage: ''
|
173
172
|
licenses:
|
174
173
|
- MIT
|
174
|
+
metadata: {}
|
175
175
|
post_install_message:
|
176
176
|
rdoc_options: []
|
177
177
|
require_paths:
|
178
178
|
- lib
|
179
179
|
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
-
none: false
|
181
180
|
requirements:
|
182
181
|
- - ! '>='
|
183
182
|
- !ruby/object:Gem::Version
|
184
183
|
version: '0'
|
185
184
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
-
none: false
|
187
185
|
requirements:
|
188
186
|
- - ! '>='
|
189
187
|
- !ruby/object:Gem::Version
|
190
188
|
version: '0'
|
191
189
|
requirements: []
|
192
190
|
rubyforge_project:
|
193
|
-
rubygems_version:
|
191
|
+
rubygems_version: 2.2.2
|
194
192
|
signing_key:
|
195
|
-
specification_version:
|
193
|
+
specification_version: 4
|
196
194
|
summary: Implements an event river mechanism for Pebblebed.
|
197
195
|
test_files:
|
198
196
|
- spec/lib/river_spec.rb
|
@@ -200,3 +198,4 @@ test_files:
|
|
200
198
|
- spec/lib/subscription_spec.rb
|
201
199
|
- spec/lib/worker_spec.rb
|
202
200
|
- spec/spec_helper.rb
|
201
|
+
has_rdoc:
|