fluffle 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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