pebbles-river 0.2.6 → 0.3.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 +4 -4
- data/lib/pebbles/river.rb +0 -1
- data/lib/pebbles/river/river.rb +29 -21
- data/lib/pebbles/river/routing.rb +35 -2
- data/lib/pebbles/river/version.rb +1 -1
- data/lib/pebbles/river/worker.rb +12 -3
- data/spec/lib/routing_spec.rb +58 -6
- metadata +3 -6
- data/lib/pebbles/river/subscription.rb +0 -44
- data/spec/lib/subscription_spec.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2e7d4334cd98eebaa0bb87f18ff27cb94c373bf
|
4
|
+
data.tar.gz: 21845c17a52a8c30aa620cf77e7d872fda365751
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9757df6f8aa96fafca5646fd75dc0b31741d80b6c49a5713b70c6a16c2a26a6fa5ec9c5a1e12bf2b6d619b99a891923f0671364f6372f83b43568b56e27bbc7
|
7
|
+
data.tar.gz: 734552e446937b0016bf4aded20221f477fc6083f6e4f378c0c633bd74f40cf7cbe38e23e203146176af684f7a374ffa62c06836d245868d449f2aeabbef89b3
|
data/lib/pebbles/river.rb
CHANGED
@@ -9,7 +9,6 @@ require_relative "river/version"
|
|
9
9
|
require_relative "river/errors"
|
10
10
|
require_relative "river/message"
|
11
11
|
require_relative "river/worker"
|
12
|
-
require_relative "river/subscription"
|
13
12
|
require_relative "river/supervisor"
|
14
13
|
require_relative "river/routing"
|
15
14
|
require_relative "river/river"
|
data/lib/pebbles/river/river.rb
CHANGED
@@ -8,12 +8,18 @@ module Pebbles
|
|
8
8
|
attr_reader :session
|
9
9
|
attr_reader :channel
|
10
10
|
attr_reader :prefetch
|
11
|
+
attr_reader :exchange_name
|
11
12
|
|
12
13
|
def initialize(options = {})
|
13
14
|
options = {environment: options} if options.is_a?(String) # Backwards compatibility
|
14
15
|
|
15
16
|
@environment = (options[:environment] || ENV['RACK_ENV'] || 'development').dup.freeze
|
17
|
+
|
18
|
+
@exchange_name = 'pebblebed.river'
|
19
|
+
@exchange_name << ".#{environment}" if @environment != 'production'
|
20
|
+
|
16
21
|
@last_connect_attempt = nil
|
22
|
+
|
17
23
|
@prefetch = options[:prefetch]
|
18
24
|
end
|
19
25
|
|
@@ -25,15 +31,13 @@ module Pebbles
|
|
25
31
|
unless @session and @channel and @exchange
|
26
32
|
disconnect
|
27
33
|
handle_session_error do
|
28
|
-
session = Bunny::Session.new(::Pebbles::River.rabbitmq_options)
|
29
|
-
session.start
|
30
|
-
|
31
|
-
channel = session.create_channel
|
32
|
-
channel.prefetch(@prefetch) if @prefetch
|
34
|
+
@session = Bunny::Session.new(::Pebbles::River.rabbitmq_options)
|
35
|
+
@session.start
|
33
36
|
|
34
|
-
|
37
|
+
@channel = @session.create_channel
|
38
|
+
@channel.prefetch(@prefetch) if @prefetch
|
35
39
|
|
36
|
-
@
|
40
|
+
@exchange = @channel.exchange(@exchange_name, EXCHANGE_OPTIONS.dup)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -70,16 +74,27 @@ module Pebbles
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def queue(options = {})
|
77
|
+
options.assert_valid_keys(:name, :ttl, :event, :path, :klass,
|
78
|
+
:dead_letter_routing_key, :routing_key)
|
79
|
+
|
73
80
|
raise ArgumentError.new 'Queue must be named' unless options[:name]
|
74
81
|
|
75
|
-
|
82
|
+
queue_args = {}
|
76
83
|
if (ttl = options[:ttl])
|
77
|
-
|
84
|
+
queue_args['x-message-ttl'] = ttl
|
85
|
+
end
|
86
|
+
if (dead_letter_routing_key = options[:dead_letter_routing_key])
|
87
|
+
queue_args['x-dead-letter-exchange'] = @exchange_name
|
88
|
+
queue_args['x-dead-letter-routing-key'] = dead_letter_routing_key
|
78
89
|
end
|
90
|
+
queue_opts = {durable: true, arguments: queue_args}
|
79
91
|
|
80
92
|
connect
|
81
93
|
queue = @channel.queue(options[:name], queue_opts)
|
82
|
-
|
94
|
+
if (routing_key = options[:routing_key])
|
95
|
+
queue.bind(exchange.name, key: routing_key)
|
96
|
+
end
|
97
|
+
Routing.binding_routing_keys_for(options.slice(:event, :class, :path)).each do |key|
|
83
98
|
queue.bind(exchange.name, key: key)
|
84
99
|
end
|
85
100
|
queue
|
@@ -87,20 +102,10 @@ module Pebbles
|
|
87
102
|
|
88
103
|
private
|
89
104
|
|
90
|
-
def exchange_name
|
91
|
-
return @exchange_name ||= format_exchange_name
|
92
|
-
end
|
93
|
-
|
94
|
-
def format_exchange_name
|
95
|
-
name = 'pebblebed.river'
|
96
|
-
name << ".#{environment}" if @environment != 'production'
|
97
|
-
name
|
98
|
-
end
|
99
|
-
|
100
105
|
def handle_session_error(exception_klass = ConnectFailure, &block)
|
101
106
|
last_exception = nil
|
102
107
|
Timeout.timeout(MAX_RETRY_TIMEOUT) do
|
103
|
-
|
108
|
+
retry_count = 0
|
104
109
|
begin
|
105
110
|
yield
|
106
111
|
rescue *CONNECTION_EXCEPTIONS => exception
|
@@ -112,6 +117,9 @@ module Pebbles
|
|
112
117
|
end
|
113
118
|
end
|
114
119
|
rescue Timeout::Error => timeout_exception
|
120
|
+
# Timeouts can screw up the connection, so forcibly close it
|
121
|
+
disconnect
|
122
|
+
|
115
123
|
if last_exception
|
116
124
|
raise exception_klass.new(last_exception.message, last_exception)
|
117
125
|
else
|
@@ -4,7 +4,6 @@ module Pebbles
|
|
4
4
|
|
5
5
|
def self.routing_key_for(options)
|
6
6
|
options.assert_valid_keys(:uid, :event)
|
7
|
-
|
8
7
|
raise ArgumentError.new(':event is required') unless options[:event]
|
9
8
|
raise ArgumentError.new(':uid is required') unless options[:uid]
|
10
9
|
|
@@ -13,6 +12,40 @@ module Pebbles
|
|
13
12
|
key.join('._.')
|
14
13
|
end
|
15
14
|
|
15
|
+
def self.binding_routing_keys_for(options)
|
16
|
+
options.assert_valid_keys(:path, :class, :event)
|
17
|
+
keys = []
|
18
|
+
if options[:event] or options[:path] or options[:class]
|
19
|
+
element_to_routing_key_parts(options[:event]).each do |event|
|
20
|
+
element_to_routing_key_parts(options[:class]).each do |klass|
|
21
|
+
element_to_routing_key_parts(options[:path]).each do |pathspec|
|
22
|
+
path_to_routing_key_parts(pathspec).each do |path|
|
23
|
+
keys << [event, klass, path].join('._.')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
keys
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.path_to_routing_key_parts(s)
|
35
|
+
required, optional = s.split('^').map { |p| p.split('.') }
|
36
|
+
required = Array(required.join('.'))
|
37
|
+
optional ||= []
|
38
|
+
(0..optional.length).map {|i| required + optional[0,i]}.map {|p| p.join('.')}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.element_to_routing_key_parts(s)
|
42
|
+
s ||= '#'
|
43
|
+
if s.respond_to?(:to_a)
|
44
|
+
s = s.join('|')
|
45
|
+
end
|
46
|
+
s.gsub('**', '#').split('|')
|
47
|
+
end
|
48
|
+
|
16
49
|
end
|
17
50
|
end
|
18
|
-
end
|
51
|
+
end
|
data/lib/pebbles/river/worker.rb
CHANGED
@@ -50,6 +50,7 @@ module Pebbles
|
|
50
50
|
else
|
51
51
|
@managed_acking = true
|
52
52
|
end
|
53
|
+
@dead_lettered = !!@queue_options[:dead_letter_routing_key]
|
53
54
|
@on_exception = options[:on_exception] || ->(*args) { }
|
54
55
|
@handler = handler
|
55
56
|
@river = River.new(options.slice(:prefetch))
|
@@ -137,7 +138,7 @@ module Pebbles
|
|
137
138
|
message = Message.new(content, delivery_info, queue)
|
138
139
|
rescue => exception
|
139
140
|
ignore_exceptions do
|
140
|
-
|
141
|
+
reject(delivery_info)
|
141
142
|
end
|
142
143
|
raise exception
|
143
144
|
else
|
@@ -148,7 +149,7 @@ module Pebbles
|
|
148
149
|
rescue => exception
|
149
150
|
if @managed_acking
|
150
151
|
ignore_exceptions do
|
151
|
-
|
152
|
+
reject(delivery_info)
|
152
153
|
end
|
153
154
|
end
|
154
155
|
raise exception
|
@@ -156,7 +157,7 @@ module Pebbles
|
|
156
157
|
if @managed_acking
|
157
158
|
case result
|
158
159
|
when false
|
159
|
-
|
160
|
+
reject(delivery_info)
|
160
161
|
else
|
161
162
|
message.ack
|
162
163
|
end
|
@@ -165,6 +166,14 @@ module Pebbles
|
|
165
166
|
end
|
166
167
|
end
|
167
168
|
|
169
|
+
def reject(delivery_info)
|
170
|
+
# Normally requeue, except if we are dead-lettering to another queue, where
|
171
|
+
# requeue = false means to bounce it to DLX.
|
172
|
+
requeue = !@dead_lettered
|
173
|
+
|
174
|
+
queue.channel.reject(delivery_info.delivery_tag.to_i, requeue)
|
175
|
+
end
|
176
|
+
|
168
177
|
def with_exceptions(&block)
|
169
178
|
begin
|
170
179
|
yield
|
data/spec/lib/routing_spec.rb
CHANGED
@@ -6,20 +6,72 @@ describe Pebbles::River::Routing do
|
|
6
6
|
Pebbles::River::Routing
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
10
|
-
|
9
|
+
describe '#routing_key_for' do
|
11
10
|
specify do
|
12
|
-
options = {:
|
11
|
+
options = {event: 'created', :uid => 'post.awesome.event:feeds.bagera.whatevs$123'}
|
13
12
|
subject.routing_key_for(options).should eq('created._.post.awesome.event._.feeds.bagera.whatevs')
|
14
13
|
end
|
15
14
|
|
16
|
-
specify
|
15
|
+
specify 'event is required' do
|
17
16
|
->{ subject.routing_key_for(:uid => 'whatevs') }.should raise_error ArgumentError
|
18
17
|
end
|
19
18
|
|
20
|
-
specify
|
21
|
-
->{ subject.routing_key_for(:
|
19
|
+
specify 'uid is required' do
|
20
|
+
->{ subject.routing_key_for(event: 'whatevs') }.should raise_error ArgumentError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#binding_routing_keys_for' do
|
25
|
+
specify 'simple, direct match' do
|
26
|
+
expect(subject.binding_routing_keys_for(event: 'create', class: 'post.event', path: 'feed.bagera')).to eq [
|
27
|
+
'create._.post.event._.feed.bagera'
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
specify 'simple wildcard match' do
|
32
|
+
options = {event: '*.create', class: 'post.*', path: '*.bagera.*'}
|
33
|
+
expect(subject.binding_routing_keys_for(options)).to eq(['*.create._.post.*._.*.bagera.*'])
|
22
34
|
end
|
23
35
|
|
36
|
+
describe "anything matchers" do
|
37
|
+
specify 'match anything (duh)' do
|
38
|
+
expect(subject.binding_routing_keys_for({event: '**', class: '**', path: '**'})).to eq(['#._.#._.#'])
|
39
|
+
end
|
40
|
+
|
41
|
+
specify 'match nothing if not specified' do
|
42
|
+
expect(subject.binding_routing_keys_for({})).to eq []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'handles "or" queries' do
|
47
|
+
options = {event: 'create|delete', class: 'post', path: 'bagera|bandwagon'}
|
48
|
+
expected = ['create._.post._.bagera', 'delete._.post._.bagera', 'create._.post._.bandwagon', 'delete._.post._.bandwagon'].sort
|
49
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
50
|
+
end
|
51
|
+
|
52
|
+
# FIXME
|
53
|
+
# describe "optional paths" do
|
54
|
+
# it { Subscription.new.pathify('a.b').should eq(['a.b']) }
|
55
|
+
# it { Subscription.new.pathify('a.^b.c').should eq(%w(a a.b a.b.c)) }
|
56
|
+
# end
|
57
|
+
|
58
|
+
it "handles optional queries" do
|
59
|
+
options = {event: 'create', class: 'post', path: 'feeds.bagera.^fb.concerts'}
|
60
|
+
expected = ['create._.post._.feeds.bagera', 'create._.post._.feeds.bagera.fb', 'create._.post._.feeds.bagera.fb.concerts'].sort
|
61
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "combines all kinds of weird stuff" do
|
65
|
+
options = {event: 'create', class: 'post', path: 'a.^b.c|x.^y.z'}
|
66
|
+
expected = [
|
67
|
+
'create._.post._.a',
|
68
|
+
'create._.post._.a.b',
|
69
|
+
'create._.post._.a.b.c',
|
70
|
+
'create._.post._.x',
|
71
|
+
'create._.post._.x.y',
|
72
|
+
'create._.post._.x.y.z',
|
73
|
+
].sort
|
74
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
75
|
+
end
|
24
76
|
end
|
25
77
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pebbles-river
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Staubo
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-07-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pebblebed
|
@@ -157,14 +157,12 @@ files:
|
|
157
157
|
- lib/pebbles/river/rate_limiter.rb
|
158
158
|
- lib/pebbles/river/river.rb
|
159
159
|
- lib/pebbles/river/routing.rb
|
160
|
-
- lib/pebbles/river/subscription.rb
|
161
160
|
- lib/pebbles/river/supervisor.rb
|
162
161
|
- lib/pebbles/river/version.rb
|
163
162
|
- lib/pebbles/river/worker.rb
|
164
163
|
- pebbles-river.gemspec
|
165
164
|
- spec/lib/river_spec.rb
|
166
165
|
- spec/lib/routing_spec.rb
|
167
|
-
- spec/lib/subscription_spec.rb
|
168
166
|
- spec/lib/worker_spec.rb
|
169
167
|
- spec/spec_helper.rb
|
170
168
|
homepage: ''
|
@@ -187,14 +185,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
185
|
version: '0'
|
188
186
|
requirements: []
|
189
187
|
rubyforge_project:
|
190
|
-
rubygems_version: 2.
|
188
|
+
rubygems_version: 2.2.5
|
191
189
|
signing_key:
|
192
190
|
specification_version: 4
|
193
191
|
summary: Implements an event river mechanism for Pebblebed.
|
194
192
|
test_files:
|
195
193
|
- spec/lib/river_spec.rb
|
196
194
|
- spec/lib/routing_spec.rb
|
197
|
-
- spec/lib/subscription_spec.rb
|
198
195
|
- spec/lib/worker_spec.rb
|
199
196
|
- spec/spec_helper.rb
|
200
197
|
has_rdoc:
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module Pebbles
|
2
|
-
module River
|
3
|
-
|
4
|
-
class Subscription
|
5
|
-
|
6
|
-
attr_reader :events, :klasses, :paths
|
7
|
-
|
8
|
-
def initialize(options = {})
|
9
|
-
@events = querify(options[:event]).split('|')
|
10
|
-
@paths = querify(options[:path]).split('|')
|
11
|
-
@klasses = querify(options[:klass]).split('|')
|
12
|
-
end
|
13
|
-
|
14
|
-
def queries
|
15
|
-
qx = []
|
16
|
-
# If we add more than one more level,
|
17
|
-
# it's probably time to go recursive.
|
18
|
-
events.each do |event|
|
19
|
-
klasses.each do |klass|
|
20
|
-
paths.each do |pathspec|
|
21
|
-
pathify(pathspec).each do |path|
|
22
|
-
qx << [event, klass, path].join('._.')
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
qx
|
28
|
-
end
|
29
|
-
|
30
|
-
def querify(query)
|
31
|
-
(query || '#').gsub('**', '#')
|
32
|
-
end
|
33
|
-
|
34
|
-
def pathify(s)
|
35
|
-
required, optional = s.split('^').map {|s| s.split('.')}
|
36
|
-
required = Array(required.join('.'))
|
37
|
-
optional ||= []
|
38
|
-
(0..optional.length).map {|i| required + optional[0,i]}.map {|p| p.join('.')}
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
include Pebbles::River
|
4
|
-
|
5
|
-
describe Subscription do
|
6
|
-
|
7
|
-
specify 'simple, direct match' do
|
8
|
-
options = {:event => 'create', :klass => 'post.event', :path => 'feed.bagera'}
|
9
|
-
subscription = Subscription.new(options)
|
10
|
-
subscription.queries.should eq(['create._.post.event._.feed.bagera'])
|
11
|
-
end
|
12
|
-
|
13
|
-
specify 'simple wildcard match' do
|
14
|
-
options = {:event => '*.create', :klass => 'post.*', :path => '*.bagera.*'}
|
15
|
-
Subscription.new(options).queries.should eq(['*.create._.post.*._.*.bagera.*'])
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "anything matchers" do
|
19
|
-
|
20
|
-
specify 'match anything (duh)' do
|
21
|
-
options = {:event => '**', :klass => '**', :path => '**'}
|
22
|
-
Subscription.new(options).queries.should eq(['#._.#._.#'])
|
23
|
-
end
|
24
|
-
|
25
|
-
specify 'match anything if not specified' do
|
26
|
-
Subscription.new.queries.should eq(['#._.#._.#'])
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'handles "or" queries' do
|
32
|
-
options = {:event => 'create|delete', :klass => 'post', :path => 'bagera|bandwagon'}
|
33
|
-
expected = ['create._.post._.bagera', 'delete._.post._.bagera', 'create._.post._.bandwagon', 'delete._.post._.bandwagon'].sort
|
34
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
35
|
-
end
|
36
|
-
|
37
|
-
describe "optional paths" do
|
38
|
-
it { Subscription.new.pathify('a.b').should eq(['a.b']) }
|
39
|
-
it { Subscription.new.pathify('a.^b.c').should eq(%w(a a.b a.b.c)) }
|
40
|
-
end
|
41
|
-
|
42
|
-
it "handles optional queries" do
|
43
|
-
options = {:event => 'create', :klass => 'post', :path => 'feeds.bagera.^fb.concerts'}
|
44
|
-
expected = ['create._.post._.feeds.bagera', 'create._.post._.feeds.bagera.fb', 'create._.post._.feeds.bagera.fb.concerts'].sort
|
45
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "combines all kinds of weird stuff" do
|
49
|
-
options = {:event => 'create', :klass => 'post', :path => 'a.^b.c|x.^y.z'}
|
50
|
-
expected = [
|
51
|
-
'create._.post._.a',
|
52
|
-
'create._.post._.a.b',
|
53
|
-
'create._.post._.a.b.c',
|
54
|
-
'create._.post._.x',
|
55
|
-
'create._.post._.x.y',
|
56
|
-
'create._.post._.x.y.z',
|
57
|
-
].sort
|
58
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|