pebbles-river 0.0.7 → 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.
- 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:
|