fluffle 0.6.3 → 0.7.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: 07e464b97aa56f6a1cac97589bb1552250ae0554
4
- data.tar.gz: f8ac0063ee89fa55bff06fa26919697dc7385136
3
+ metadata.gz: 9edeb50743d908defa90bd84bd3eb7fdb4e758d7
4
+ data.tar.gz: 08e15dce619174c1d68a8cad7ef9f4594db34de7
5
5
  SHA512:
6
- metadata.gz: bc1577fe0a41bbefeae03a2abceba360ccddfa5ffb0b6cb4a0245056ac5a1adb7b4b98ec4f43381567ec2dc5f91726514109c518469c4a718d0869220fd386a3
7
- data.tar.gz: 8858820d0c5ad70f8427474bb1c6ac7bf462278b1f4fe7d00ec915bf5d8de094be3d712158bd488599ec6b2ea9dc874af60398ed501d2fe6312748fbc0dc1877
6
+ metadata.gz: 39a86ec46f7a0de63792e1331980fda6f0938fa14e465e69b08e12f8a50133b69bf072db4c1db73324fc201c8a9ac073cde88e65c0b43e195f03a5e4c5782725
7
+ data.tar.gz: 16faf0e8c3196225712ada026f3bc6975f3c0fac79d38b9ebc93f509e898f82d020575b69867107ad0240f57b662b003e024919fa104a5d24f058b8a63f4f4e4
data/examples/client.rb CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require 'fluffle'
5
5
 
6
- client = Fluffle::Client.new url: 'amqp://localhost', confirms: true
6
+ client = Fluffle::Client.new url: 'amqp://localhost', confirms: true, mandatory: true
7
7
  # You can also pass `connection:` to use an existing Bunny connection:
8
8
  # Fluffle::Client.new(connection: Bunny.new('amqp://localhost', heartbeat: 2))
9
9
 
data/examples/server.rb CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require 'fluffle'
5
5
 
6
- server = Fluffle::Server.new url: 'amqp://localhost', concurrency: 5
6
+ server = Fluffle::Server.new url: 'amqp://localhost', concurrency: 5, confirms: true
7
7
 
8
8
  server.drain do |dispatcher|
9
9
  dispatcher.handle('foo') { 'bar' }
data/lib/fluffle.rb CHANGED
@@ -3,10 +3,12 @@ require 'logger'
3
3
 
4
4
  require 'fluffle/version'
5
5
  require 'fluffle/client'
6
+ require 'fluffle/confirmer'
6
7
  require 'fluffle/errors'
7
8
  require 'fluffle/handlers/base'
8
9
  require 'fluffle/handlers/delegator'
9
10
  require 'fluffle/handlers/dispatcher'
11
+ require 'fluffle/middleware_stack'
10
12
  require 'fluffle/server'
11
13
 
12
14
  module Fluffle
@@ -10,13 +10,15 @@ module Fluffle
10
10
  include Connectable
11
11
 
12
12
  attr_reader :confirms
13
+ attr_reader :mandatory
13
14
  attr_accessor :default_timeout
14
15
  attr_accessor :logger
15
16
 
16
- def initialize(url: nil, connection: nil, confirms: false)
17
+ def initialize(url: nil, connection: nil, confirms: false, mandatory: false)
17
18
  self.connect(url || connection)
18
19
 
19
20
  @confirms = confirms
21
+ @mandatory = mandatory
20
22
  @default_timeout = 5
21
23
  @logger = Fluffle.logger
22
24
 
@@ -29,8 +31,12 @@ module Fluffle
29
31
  @prng = Random.new
30
32
 
31
33
  if confirms
32
- @pending_confirms = Concurrent::Map.new
33
- confirm_select
34
+ @confirmer = Fluffle::Confirmer.new channel: @channel
35
+ @confirmer.confirm_select
36
+ end
37
+
38
+ if mandatory
39
+ handle_returns
34
40
  end
35
41
 
36
42
  @pending_responses = Concurrent::Map.new
@@ -51,20 +57,18 @@ module Fluffle
51
57
  end
52
58
  end
53
59
 
54
- def confirm_select
55
- handle_confirm = ->(tag, _multiple, nack) do
56
- ivar = @pending_confirms.delete tag
60
+ def handle_returns
61
+ @exchange.on_return do |return_info, properties, payload|
62
+ id = properties[:correlation_id]
63
+ ivar = @pending_responses.delete id
57
64
 
58
65
  if ivar
59
- ivar.set nack
60
- else
61
- self.logger.error "Missing confirm IVar: tag=#{tag}"
66
+ message = Kernel.sprintf "Received return from exchange for routing key `%s' (%d %s)", return_info.routing_key, return_info.reply_code, return_info.reply_text
67
+
68
+ error = Fluffle::Errors::ReturnError.new message
69
+ ivar.set error
62
70
  end
63
71
  end
64
-
65
- # Set the channel in confirmation mode so that we can receive confirms
66
- # of published messages
67
- @channel.confirm_select handle_confirm
68
72
  end
69
73
 
70
74
  # Fetch and set the `IVar` with a response from the server. This method is
@@ -129,14 +133,27 @@ module Fluffle
129
133
  response_ivar = Concurrent::IVar.new
130
134
  @pending_responses[id] = response_ivar
131
135
 
136
+ stack = Fluffle::MiddlewareStack.new
137
+
132
138
  if confirms
133
- with_confirmation(timeout: timeout) { publish payload, queue: queue }
134
- else
139
+ stack.push ->(publish) do
140
+ @confirmer.with_confirmation timeout: timeout, &publish
141
+ end
142
+ end
143
+
144
+ stack.call do
135
145
  publish payload, queue: queue
136
146
  end
137
147
 
138
148
  response = response_ivar.value timeout
139
- raise_incomplete(payload, 'response') if response_ivar.incomplete?
149
+
150
+ if response_ivar.incomplete?
151
+ raise Errors::TimeoutError.new("Timed out waiting for response to `#{describe_payload(payload)}'")
152
+ elsif response.is_a? StandardError
153
+ # Exchange returns will preempt the response and set it to an error
154
+ # that we can raise
155
+ raise response
156
+ end
140
157
 
141
158
  return response
142
159
  ensure
@@ -153,35 +170,12 @@ module Fluffle
153
170
  "#{method}/#{arity}"
154
171
  end
155
172
 
156
- # Wraps a block (which should publish a message) with a blocking check
157
- # that the client received a confirmation from the RabbitMQ server
158
- # that the message that was received and routed successfully
159
- def with_confirmation(timeout:)
160
- tag = @channel.next_publish_seq_no
161
- confirm_ivar = Concurrent::IVar.new
162
- @pending_confirms[tag] = confirm_ivar
163
-
164
- yield
165
-
166
- nack = confirm_ivar.value timeout
167
- if confirm_ivar.incomplete?
168
- raise_incomplete payload, 'confirm'
169
- elsif nack
170
- raise Errors::NackError.new('Received nack from confirmation')
171
- end
172
- end
173
-
174
- # event_name - String describing what we timed out waiting for, should
175
- # be 'response' or 'confirm'
176
- def raise_incomplete(payload, event_name)
177
- raise Errors::TimeoutError.new("Timed out waiting for #{event_name} to `#{describe_payload(payload)}'")
178
- end
179
-
180
173
  def publish(payload, queue:)
181
174
  opts = {
182
175
  routing_key: Fluffle.request_queue_name(queue),
183
176
  correlation_id: payload['id'],
184
- reply_to: @reply_queue.name
177
+ reply_to: @reply_queue.name,
178
+ mandatory: @mandatory,
185
179
  }
186
180
 
187
181
  @exchange.publish Oj.dump(payload), opts
@@ -0,0 +1,49 @@
1
+ module Fluffle
2
+ class Confirmer
3
+ attr_reader :channel
4
+
5
+ def initialize(channel:)
6
+ @channel = channel
7
+
8
+ @pending_confirms = Concurrent::Map.new
9
+ end
10
+
11
+ # Enables confirms on the channel and sets up callback to receive and
12
+ # unblock corresponding `with_confirmation` call.
13
+ def confirm_select
14
+ handle_confirm = ->(tag, _multiple, nack) do
15
+ ivar = @pending_confirms.delete tag
16
+
17
+ if ivar
18
+ ivar.set nack
19
+ else
20
+ self.logger.error "Missing confirm IVar: tag=#{tag}"
21
+ end
22
+ end
23
+
24
+ # Set the channel in confirmation mode so that we can receive confirms
25
+ # of published messages
26
+ @channel.confirm_select handle_confirm
27
+ end
28
+
29
+ # Wraps a block (which should publish a message) with a blocking check
30
+ # that it received a confirmation from the RabbitMQ server that the
31
+ # message that was received and routed successfully.
32
+ def with_confirmation(timeout:)
33
+ tag = @channel.next_publish_seq_no
34
+ confirm_ivar = Concurrent::IVar.new
35
+ @pending_confirms[tag] = confirm_ivar
36
+
37
+ result = yield
38
+
39
+ nack = confirm_ivar.value timeout
40
+ if confirm_ivar.incomplete?
41
+ raise Errors::TimeoutError.new('Timed out waiting for confirm')
42
+ elsif nack
43
+ raise Errors::NackError.new('Received nack from confirmation')
44
+ end
45
+
46
+ result
47
+ end
48
+ end
49
+ end
@@ -13,6 +13,10 @@ module Fluffle
13
13
  class TimeoutError < StandardError
14
14
  end
15
15
 
16
+ # Raised if it received a return from the server
17
+ class ReturnError < StandardError
18
+ end
19
+
16
20
  # Raise this within your own code to get an error that will be faithfully
17
21
  # translated into the code, message, and data member fields of the
18
22
  # spec's `Error` response object
@@ -0,0 +1,34 @@
1
+ module Fluffle
2
+ class MiddlewareStack
3
+ def initialize
4
+ @stack = []
5
+ end
6
+
7
+ def push(middleware)
8
+ @stack << middleware
9
+ self
10
+ end
11
+
12
+ alias_method :<<, :push
13
+
14
+ # Calls the stack in FIFO order with the callable (passed as an object
15
+ # receiving `#call` or an `&block`) being called last.
16
+ #
17
+ # For example:
18
+ # stack.push 1
19
+ # stack.push 2
20
+ # stack.call 3
21
+ #
22
+ # Will be evaluated 1 -> 2 -> 3 -> 2 -> 1.
23
+ def call(callable = nil, &block)
24
+ callable ||= block
25
+
26
+ @stack
27
+ .reverse
28
+ .inject(callable) { |previous, middleware|
29
+ ->{ middleware.call(previous) }
30
+ }
31
+ .call
32
+ end
33
+ end
34
+ end
@@ -1,15 +1,23 @@
1
+ require 'concurrent'
2
+ require 'oj'
3
+
1
4
  module Fluffle
2
5
  class Server
3
6
  include Connectable
4
7
 
5
- attr_reader :connection, :handlers, :handler_pool
8
+ attr_reader :confirms, :connection, :handlers, :handler_pool
9
+ attr_accessor :publish_timeout
6
10
 
7
11
  # url: - Optional URL to pass to `Bunny.new` to immediately connect
8
12
  # concurrency: - Number of threads to handle messages on (default: 1)
9
- def initialize(url: nil, connection: nil, concurrency: 1)
13
+ # confirms: - Whether or not to use RabbitMQ confirms
14
+ def initialize(url: nil, connection: nil, concurrency: 1, confirms: false)
10
15
  url_or_connection = url || connection
11
16
  self.connect(url_or_connection) if url_or_connection
12
17
 
18
+ @confirms = confirms
19
+ @publish_timeout = 5
20
+
13
21
  @handlers = {}
14
22
  @handler_pool = Concurrent::FixedThreadPool.new concurrency
15
23
 
@@ -27,13 +35,22 @@ module Fluffle
27
35
 
28
36
  handler = Fluffle::Handlers::Dispatcher.new(&block) if block
29
37
 
38
+ raise ArgumentError, 'Handler cannot be nil' if handler.nil?
39
+
30
40
  @handlers[queue.to_s] = handler
31
41
  end
32
42
 
33
43
  def start
44
+ @handlers.freeze
45
+
34
46
  @channel = @connection.create_channel
35
47
  @exchange = @channel.default_exchange
36
48
 
49
+ if confirms
50
+ @confirmer = Fluffle::Confirmer.new channel: @channel
51
+ @confirmer.confirm_select
52
+ end
53
+
37
54
  raise 'No handlers defined' if @handlers.empty?
38
55
 
39
56
  @handlers.each do |name, handler|
@@ -102,8 +119,18 @@ module Fluffle
102
119
  }
103
120
  end
104
121
 
105
- @exchange.publish Oj.dump(response), routing_key: reply_to,
106
- correlation_id: response['id']
122
+ stack = Fluffle::MiddlewareStack.new
123
+
124
+ if confirms
125
+ stack.push ->(publish) {
126
+ @confirmer.with_confirmation timeout: publish_timeout, &publish
127
+ }
128
+ end
129
+
130
+ stack.call do
131
+ @exchange.publish Oj.dump(response), routing_key: reply_to,
132
+ correlation_id: response['id']
133
+ end
107
134
  end
108
135
 
109
136
  # handler - Instance of a `Handler` that may receive `#call`
@@ -167,6 +167,9 @@ module Fluffle
167
167
  def publish(payload, opts)
168
168
  @channel.publish payload, opts
169
169
  end
170
+
171
+ def on_return(&block)
172
+ end
170
173
  end
171
174
 
172
175
  class Queue
@@ -1,3 +1,3 @@
1
1
  module Fluffle
2
- VERSION = '0.6.3'
2
+ VERSION = '0.7.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluffle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dirk Gadsden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-28 00:00:00.000000000 Z
11
+ date: 2016-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -129,11 +129,13 @@ files:
129
129
  - fluffle.jpg
130
130
  - lib/fluffle.rb
131
131
  - lib/fluffle/client.rb
132
+ - lib/fluffle/confirmer.rb
132
133
  - lib/fluffle/connectable.rb
133
134
  - lib/fluffle/errors.rb
134
135
  - lib/fluffle/handlers/base.rb
135
136
  - lib/fluffle/handlers/delegator.rb
136
137
  - lib/fluffle/handlers/dispatcher.rb
138
+ - lib/fluffle/middleware_stack.rb
137
139
  - lib/fluffle/railtie.rb
138
140
  - lib/fluffle/server.rb
139
141
  - lib/fluffle/testing.rb