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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 810c3766a5dca1d09e230dd2fa33b2cfc2f17a6e
4
- data.tar.gz: 907a549065e1ae8013cfa6738466c92dbaa5e4ef
3
+ metadata.gz: e2e7d4334cd98eebaa0bb87f18ff27cb94c373bf
4
+ data.tar.gz: 21845c17a52a8c30aa620cf77e7d872fda365751
5
5
  SHA512:
6
- metadata.gz: ea19b805fe8d0c09f826a0cd6f52bdf155173d2c530347fa04c487baecd7a2924a1aac866b1dfb0fccfbde1c2de18790631c416566157ff6125b431256aee347
7
- data.tar.gz: 117fcfcf720e0de40ed98f6f37f0b1a9bf037748fcb9e1e8de84039dc3e9e9d0a21ef791b5fa8ef66626523471607f4ceced93cbce692ad47f7b3dd347b82df3
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"
@@ -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
- exchange = channel.exchange(exchange_name, EXCHANGE_OPTIONS.dup)
37
+ @channel = @session.create_channel
38
+ @channel.prefetch(@prefetch) if @prefetch
35
39
 
36
- @session, @channel, @exchange = session, channel, exchange
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
- queue_opts = {durable: true}
82
+ queue_args = {}
76
83
  if (ttl = options[:ttl])
77
- queue_opts[:arguments] = {'x-message-ttl' => ttl}
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
- Subscription.new(options).queries.each do |key|
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
- retry_until, retry_count = nil, 0
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
@@ -1,5 +1,5 @@
1
1
  module Pebbles
2
2
  module River
3
- VERSION = '0.2.6'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -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
- queue.channel.nack(delivery_info, false, true)
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
- message.nack
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
- message.nack
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
@@ -6,20 +6,72 @@ describe Pebbles::River::Routing do
6
6
  Pebbles::River::Routing
7
7
  end
8
8
 
9
- describe "routing keys" do
10
-
9
+ describe '#routing_key_for' do
11
10
  specify do
12
- options = {:event => 'created', :uid => 'post.awesome.event:feeds.bagera.whatevs$123'}
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 "event is required" do
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 "uid is required" do
21
- ->{ subject.routing_key_for(:event => 'whatevs') }.should raise_error ArgumentError
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.2.6
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: 2015-08-02 00:00:00.000000000 Z
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.4.6
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