rabbitmq 0.1.1 → 0.2.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: c4e267fdbae8d3ad4e67c1ad608a98f28ad53310
4
- data.tar.gz: 11a314ccc79d99a1d1fadcb33c0b2faa5beca282
3
+ metadata.gz: a6084d87d4d4379929fbae34f8a6d7e8f979f176
4
+ data.tar.gz: 6312eaee4b20b758986c03fa8e448f763e03cb61
5
5
  SHA512:
6
- metadata.gz: 22d8f50492ac62a77b12f403117db899939f00857275a66f2a769e367fa01bde2022fd929c0195e38c04d6b3d5ac88f71e61f6227dbbb0480e21762204617997
7
- data.tar.gz: 112db78913fb348600cf4fd468b45274da40da276ea0cbbcd150ab6bc159c840793273cff30522ee6f5c70e60c16631762dd8e8a8131c14982e6a7a6e79de192
6
+ metadata.gz: 0de8d7059d8117341d3605ce4f58463fb912dc581572821cd9b3bcc80f82b3a5cb0ecfc5c3540dae0c6aeea1071027e4d6bc366ae651e04a76d9c60b788e836f
7
+ data.tar.gz: e09ce58387e226cd673b51a32934cb8456ba9eaddb00031cafddb7fafa84ea8acbd5657789645eb3388954c24072cf6a25b8e564f38071a7b3a60f2c91e0961d
@@ -32,7 +32,7 @@ file_task 'rabbitmq-c', :download => :download_tarball do
32
32
  end
33
33
 
34
34
  file_task 'config.status', :configure => :download do
35
- system "bash -c 'cd #{FILES[:download]} && ./configure'"
35
+ system "bash -c 'cd #{FILES[:download]} && env CFLAGS=-g ./configure'"
36
36
  system "cp #{FILES[:download]}/#{FILES[:configure]} ./"
37
37
  end
38
38
 
data/lib/rabbitmq.rb CHANGED
@@ -1,8 +1,11 @@
1
1
 
2
+ require_relative 'rabbitmq/util'
3
+
2
4
  require_relative 'rabbitmq/ffi'
3
5
  require_relative 'rabbitmq/ffi/ext'
4
6
  require_relative 'rabbitmq/ffi/error'
5
7
 
6
- require_relative 'rabbitmq/util'
8
+ require_relative 'rabbitmq/server_error'
7
9
 
8
10
  require_relative 'rabbitmq/connection'
11
+ require_relative 'rabbitmq/channel'
@@ -0,0 +1,261 @@
1
+
2
+ module RabbitMQ
3
+ class Channel
4
+
5
+ attr_reader :connection
6
+ attr_reader :id
7
+
8
+ # Don't create a {Channel} directly; call {Connection#channel} instead.
9
+ # @api private
10
+ def initialize(connection, id, pre_allocated: false)
11
+ @connection = connection
12
+ @id = id
13
+ connection.send(:allocate_channel, id) unless pre_allocated
14
+
15
+ @finalizer = self.class.send :create_finalizer_for, @connection, @id
16
+ ObjectSpace.define_finalizer self, @finalizer
17
+ end
18
+
19
+ # Release the channel id to be reallocated to another {Channel} instance.
20
+ # This will be called automatically by the object finalizer after
21
+ # the object becomes unreachable by the VM and is garbage collected,
22
+ # but you may want to call it explicitly if you plan to reuse the same
23
+ # channel if in another {Channel} instance explicitly.
24
+ #
25
+ # @return [Channel] self.
26
+ #
27
+ def release
28
+ if @finalizer
29
+ @finalizer.call
30
+ ObjectSpace.undefine_finalizer self
31
+ end
32
+ @finalizer = nil
33
+
34
+ self
35
+ end
36
+
37
+ # @see {Connection#on_event}
38
+ def on(*args, &block)
39
+ @connection.on_event(@id, *args, &block)
40
+ end
41
+
42
+ # @see {Connection#run_loop!}
43
+ def run_loop!(*args)
44
+ @connection.run_loop!(*args)
45
+ end
46
+
47
+ # @see {Connection#break!}
48
+ def break!
49
+ @connection.break!
50
+ end
51
+
52
+ # Create a finalizer not entangled with the {Channel} instance.
53
+ # @api private
54
+ def self.create_finalizer_for(connection, id)
55
+ Proc.new do
56
+ connection.send(:release_channel, id)
57
+ end
58
+ end
59
+
60
+ private def rpc(request_type, params=[{}], response_type)
61
+ @connection.send_request(@id, request_type, params.last)
62
+ if response_type
63
+ @connection.fetch_response(@id, response_type)
64
+ else
65
+ true
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Exchange operations
71
+
72
+ def exchange_declare(name, type, **opts)
73
+ rpc :exchange_declare, [
74
+ exchange: name,
75
+ type: type,
76
+ passive: opts.fetch(:passive, false),
77
+ durable: opts.fetch(:durable, false),
78
+ auto_delete: opts.fetch(:auto_delete, false),
79
+ internal: opts.fetch(:internal, false),
80
+ ], :exchange_declare_ok
81
+ end
82
+
83
+ def exchange_delete(name, **opts)
84
+ rpc :exchange_delete, [
85
+ exchange: name,
86
+ if_unused: opts.fetch(:if_unused, false),
87
+ ], :exchange_delete_ok
88
+ end
89
+
90
+ def exchange_bind(source, destination, **opts)
91
+ rpc :exchange_bind, [
92
+ source: source,
93
+ destination: destination,
94
+ routing_key: opts.fetch(:routing_key, ""),
95
+ arguments: opts.fetch(:arguments, {}),
96
+ ], :exchange_bind_ok
97
+ end
98
+
99
+ def exchange_unbind(source, destination, **opts)
100
+ rpc :exchange_unbind, [
101
+ source: source,
102
+ destination: destination,
103
+ routing_key: opts.fetch(:routing_key, ""),
104
+ arguments: opts.fetch(:arguments, {}),
105
+ ], :exchange_unbind_ok
106
+ end
107
+
108
+ ##
109
+ # Queue operations
110
+
111
+ def queue_declare(name, **opts)
112
+ rpc :queue_declare, [
113
+ queue: name,
114
+ passive: opts.fetch(:passive, false),
115
+ durable: opts.fetch(:durable, false),
116
+ exclusive: opts.fetch(:exclusive, false),
117
+ auto_delete: opts.fetch(:auto_delete, false),
118
+ arguments: opts.fetch(:arguments, {}),
119
+ ], :queue_declare_ok
120
+ end
121
+
122
+ def queue_bind(name, exchange, **opts)
123
+ rpc :queue_bind, [
124
+ queue: name,
125
+ exchange: exchange,
126
+ routing_key: opts.fetch(:routing_key, ""),
127
+ arguments: opts.fetch(:arguments, {}),
128
+ ], :queue_bind_ok
129
+ end
130
+
131
+ def queue_unbind(name, exchange, **opts)
132
+ rpc :queue_unbind, [
133
+ queue: name,
134
+ exchange: exchange,
135
+ routing_key: opts.fetch(:routing_key, ""),
136
+ arguments: opts.fetch(:arguments, {}),
137
+ ], :queue_unbind_ok
138
+ end
139
+
140
+ def queue_purge(name)
141
+ rpc :queue_purge, [queue: name], :queue_purge_ok
142
+ end
143
+
144
+ def queue_delete(name, **opts)
145
+ rpc :queue_delete, [
146
+ queue: name,
147
+ if_unused: opts.fetch(:if_unused, false),
148
+ if_empty: opts.fetch(:if_empty, false),
149
+ ], :queue_delete_ok
150
+ end
151
+
152
+ ##
153
+ # Consumer operations
154
+
155
+ def basic_qos(**opts)
156
+ rpc :basic_qos, [
157
+ prefetch_count: opts.fetch(:prefetch_count, 0),
158
+ prefetch_size: opts.fetch(:prefetch_size, 0),
159
+ global: opts.fetch(:global, false),
160
+ ], :basic_qos_ok
161
+ end
162
+
163
+ def basic_consume(queue, consumer_tag="", **opts)
164
+ rpc :basic_consume, [
165
+ queue: queue,
166
+ consumer_tag: consumer_tag,
167
+ no_local: opts.fetch(:no_local, false),
168
+ no_ack: opts.fetch(:no_ack, false),
169
+ exclusive: opts.fetch(:exclusive, false),
170
+ arguments: opts.fetch(:arguments, {}),
171
+ ], :basic_consume_ok
172
+ end
173
+
174
+ def basic_cancel(consumer_tag)
175
+ rpc :basic_cancel, [consumer_tag: consumer_tag], :basic_cancel_ok
176
+ end
177
+
178
+ ##
179
+ # Transaction operations
180
+
181
+ def tx_select
182
+ rpc :tx_select, [], :tx_select_ok
183
+ end
184
+
185
+ def tx_commit
186
+ rpc :tx_commit, [], :tx_commit_ok
187
+ end
188
+
189
+ def tx_rollback
190
+ rpc :tx_rollback, [], :tx_rollback_ok
191
+ end
192
+
193
+ ##
194
+ # Message operations
195
+
196
+ def basic_get(queue, **opts)
197
+ rpc :basic_get, [
198
+ queue: queue,
199
+ no_ack: opts.fetch(:no_ack, false),
200
+ ], [:basic_get_ok, :basic_get_empty]
201
+ end
202
+
203
+ def basic_ack(delivery_tag, **opts)
204
+ rpc :basic_ack, [
205
+ delivery_tag: delivery_tag,
206
+ multiple: opts.fetch(:multiple, false),
207
+ ], nil
208
+ end
209
+
210
+ def basic_nack(delivery_tag, **opts)
211
+ rpc :basic_nack, [
212
+ delivery_tag: delivery_tag,
213
+ multiple: opts.fetch(:multiple, false),
214
+ requeue: opts.fetch(:requeue, true),
215
+ ], nil
216
+ end
217
+
218
+ def basic_reject(delivery_tag, **opts)
219
+ rpc :basic_reject, [
220
+ delivery_tag: delivery_tag,
221
+ requeue: opts.fetch(:requeue, true),
222
+ ], nil
223
+ end
224
+
225
+ def basic_publish(body, exchange, routing_key, **opts)
226
+ body = FFI::Bytes.from_s(body, true)
227
+ exchange = FFI::Bytes.from_s(exchange, true)
228
+ routing_key = FFI::Bytes.from_s(routing_key, true)
229
+ properties = FFI::BasicProperties.new.apply(
230
+ content_type: opts.fetch(:content_type, "application/octet-stream"),
231
+ content_encoding: opts.fetch(:content_encoding, ""),
232
+ headers: opts.fetch(:headers, {}),
233
+ delivery_mode: (opts.fetch(:persistent, false) ? :persistent : :nonpersistent),
234
+ priority: opts.fetch(:priority, 0),
235
+ correlation_id: opts.fetch(:correlation_id, ""),
236
+ reply_to: opts.fetch(:reply_to, ""),
237
+ expiration: opts.fetch(:expiration, ""),
238
+ message_id: opts.fetch(:message_id, ""),
239
+ timestamp: opts.fetch(:timestamp, 0),
240
+ type: opts.fetch(:type, ""),
241
+ user_id: opts.fetch(:user_id, ""),
242
+ app_id: opts.fetch(:app_id, ""),
243
+ cluster_id: opts.fetch(:cluster_id, "")
244
+ )
245
+
246
+ Util.error_check :"publishing a message",
247
+ FFI.amqp_basic_publish(connection.send(:ptr), @id,
248
+ exchange,
249
+ routing_key,
250
+ opts.fetch(:mandatory, false),
251
+ opts.fetch(:immediate, false),
252
+ properties,
253
+ body
254
+ )
255
+
256
+ properties.free!
257
+ true
258
+ end
259
+
260
+ end
261
+ end
@@ -1,9 +1,17 @@
1
1
 
2
+ require 'socket'
3
+
2
4
  module RabbitMQ
3
5
  class Connection
4
6
  def initialize *args
5
- @ptr = FFI.amqp_new_connection
6
- parse_info(*args)
7
+ @ptr = FFI.amqp_new_connection
8
+ @info = Util.connection_info(*args)
9
+
10
+ @open_channels = {}
11
+ @released_channels = {}
12
+ @event_handlers = Hash.new { |h,k| h[k] = {} }
13
+ @incoming_events = Hash.new { |h,k| h[k] = {} }
14
+
7
15
  create_socket!
8
16
 
9
17
  @finalizer = self.class.send :create_finalizer_for, @ptr
@@ -35,37 +43,122 @@ module RabbitMQ
35
43
  def port; @info[:port]; end
36
44
  def ssl?; @info[:ssl]; end
37
45
 
38
- def max_channels; @max_channels ||= 0; end; attr_writer :max_channels
39
- def max_frame_size; @max_frame_size ||= 131072; end; attr_writer :max_frame_size
46
+ def protocol_timeout
47
+ @protocol_timeout ||= 30 # seconds
48
+ end
49
+ attr_writer :protocol_timeout
50
+
51
+ def max_channels
52
+ @max_channels ||= FFI::CHANNEL_MAX_ID
53
+ end
54
+ attr_writer :max_channels
55
+
56
+ def max_frame_size
57
+ @max_frame_size ||= 131072
58
+ end
59
+ attr_writer :max_frame_size
60
+
40
61
  def heartbeat_interval; 0; end # not fully implemented in librabbitmq
41
62
 
42
63
  def start
43
64
  close # Close if already open
44
65
  connect_socket!
45
66
  login!
46
- open_channel!
67
+
68
+ self
47
69
  end
48
70
 
49
71
  def close
50
72
  raise DestroyedError unless @ptr
51
73
  FFI.amqp_connection_close(@ptr, 200)
74
+
75
+ release_all_channels
76
+
77
+ self
52
78
  end
53
79
 
54
- private def parse_info url=nil
55
- info = FFI::ConnectionInfo.new
80
+ # Send a request on the given channel with the given type and properties.
81
+ #
82
+ # @param channel_id [Integer] The channel number to send on.
83
+ # @param method [Symbol] The type of protocol method to send.
84
+ # @param properties [Hash] The properties to apply to the method.
85
+ # @raise [RabbitMQ::FFI::Error] if a library exception occurs.
86
+ #
87
+ def send_request(channel_id, method, properties={})
88
+ Util.error_check :"sending a request",
89
+ send_request_internal(Integer(channel_id), method.to_sym, properties)
56
90
 
57
- if url
58
- url_ptr = Util.strdup_ptr(url)
59
- Util.error_check :"parsing connection URL",
60
- FFI.amqp_parse_url(url_ptr, info)
61
-
62
- # We must copy ConnectionInfo before the url_ptr is freed.
63
- @info = info.to_h
64
- url_ptr.free
65
- else
66
- FFI.amqp_default_connection_info(info)
67
- @info = info.to_h
68
- end
91
+ nil
92
+ end
93
+
94
+ # Wait for a specific response on the given channel of the given type
95
+ # and return the event data for the response when it is received.
96
+ # Any other events received will be processed or stored internally.
97
+ #
98
+ # @param channel_id [Integer] The channel number to watch for.
99
+ # @param method [Symbol,Array<Symbol>] The protocol method(s) to watch for.
100
+ # @param timeout [Float] The maximum time to wait for a response in seconds;
101
+ # uses the value of {#protocol_timeout} by default.
102
+ # @raise [RabbitMQ::ServerError] if any error event is received.
103
+ # @raise [RabbitMQ::FFI::Error::Timeout] if no event is received.
104
+ # @raise [RabbitMQ::FFI::Error] if a library exception occurs.
105
+ # @return [Hash] the response data received.
106
+ #
107
+ def fetch_response(channel_id, method, timeout=protocol_timeout)
108
+ methods = Array(method).map(&:to_sym)
109
+ fetch_response_internal(Integer(channel_id), methods, Float(timeout))
110
+ end
111
+
112
+ # Register a handler for events on the given channel of the given type.
113
+ #
114
+ # @param channel_id [Integer] The channel number to watch for.
115
+ # @param method [Symbol] The type of protocol method to watch for.
116
+ # @param callable [#call,nil] The callable handler if no block is given.
117
+ # @param block [Proc,nil] The handler block to register.
118
+ # @return [Proc,#call] The given block or callable.
119
+ # @yieldparam event [Hash] The event passed to the handler.
120
+ #
121
+ def on_event(channel_id, method, callable=nil, &block)
122
+ handler = block || callable
123
+ raise ArgumentError, "expected block or callable as event handler" \
124
+ unless handler.respond_to?(:call)
125
+
126
+ @event_handlers[Integer(channel_id)][method.to_sym] = handler
127
+ handler
128
+ end
129
+
130
+ # Fetch and handle events in a loop that blocks the calling thread.
131
+ # The loop will continue until the {#break!} method is called from within
132
+ # an event handler, or until the given timeout duration has elapsed.
133
+ #
134
+ # @param timeout [Float] the maximum time to run the loop, in seconds;
135
+ # if none is given, the loop will block indefinitely or until {#break!}.
136
+ #
137
+ def run_loop!(timeout=nil)
138
+ timeout = Float(timeout) if timeout
139
+ @breaking = false
140
+ fetch_events(timeout)
141
+ nil
142
+ end
143
+
144
+ # Stop iterating from within an execution of the {#run_loop!} method.
145
+ # Call this method only from within an event handler.
146
+ # It will take effect only after the handler finishes running.
147
+ #
148
+ # @return [nil]
149
+ #
150
+ def break!
151
+ @breaking = true
152
+ nil
153
+ end
154
+
155
+ def channel(id=nil)
156
+ Channel.new(self, allocate_channel(id), pre_allocated: true)
157
+ end
158
+
159
+ private def ptr
160
+ raise DestroyedError unless @ptr
161
+ @ptr
69
162
  end
70
163
 
71
164
  private def create_socket!
@@ -82,31 +175,206 @@ module RabbitMQ
82
175
  create_socket!
83
176
  Util.error_check :"opening a socket",
84
177
  FFI.amqp_socket_open(@socket, host, port)
178
+
179
+ @ruby_socket = Socket.for_fd(FFI.amqp_get_sockfd(@ptr))
180
+ @ruby_socket.autoclose = false
85
181
  end
86
182
 
87
183
  private def login!
88
184
  raise DestroyedError unless @ptr
89
185
 
90
- rpc_check :"logging in",
91
- FFI.amqp_login(@ptr, vhost, max_channels, max_frame_size,
92
- heartbeat_interval, :plain, :string, user, :string, password)
186
+ res = FFI.amqp_login(@ptr, vhost, max_channels, max_frame_size,
187
+ heartbeat_interval, :plain, :string, user, :string, password)
188
+
189
+ case res[:reply_type]
190
+ when :library_exception; Util.error_check :"logging in", res[:library_error]
191
+ when :server_exception; raise NotImplementedError
192
+ end
93
193
 
94
194
  @server_properties = FFI::Table.new(FFI.amqp_get_server_properties(@ptr)).to_h
95
195
  end
96
196
 
97
- private def open_channel!(number=1)
197
+ private def open_channel(id)
198
+ Util.error_check :"opening a new channel",
199
+ send_request_internal(id, :channel_open)
200
+
201
+ fetch_response(id, :channel_open_ok)
202
+ end
203
+
204
+ private def reopen_channel(id)
205
+ Util.error_check :"acknowledging server-initated channel closure",
206
+ send_request_internal(id, :channel_close_ok)
207
+
208
+ Util.error_check :"reopening channel after server-initated closure",
209
+ send_request_internal(id, :channel_open)
210
+
211
+ fetch_response(id, :channel_open_ok)
212
+ end
213
+
214
+ private def allocate_channel(id=nil)
215
+ if id
216
+ raise ArgumentError, "channel #{id} is already in use" if @open_channels[id]
217
+ elsif @released_channels.empty?
218
+ id = (@open_channels.keys.sort.last || 0) + 1
219
+ else
220
+ id = @released_channels.keys.first
221
+ end
222
+ raise ArgumentError, "channel #{id} is too high" if id > max_channels
223
+
224
+ already_open = @released_channels.delete(id)
225
+ open_channel(id) unless already_open
226
+
227
+ @open_channels[id] = true
228
+ @event_handlers[id] ||= {}
229
+
230
+ id
231
+ end
232
+
233
+ private def release_channel(id)
234
+ @open_channels.delete(id)
235
+ @event_handlers.delete(id)
236
+ @released_channels[id] = true
237
+ end
238
+
239
+ private def release_all_channels
240
+ @open_channels.clear
241
+ @event_handlers.clear
242
+ @released_channels.clear
243
+ end
244
+
245
+ # Block until there is readable data on the internal ruby socket,
246
+ # returning true if there is readable data, or false if time expired.
247
+ private def select(timeout=0, start=Time.now)
248
+ if timeout
249
+ timeout = timeout - (start-Time.now)
250
+ timeout = 0 if timeout < 0
251
+ end
252
+
253
+ IO.select([@ruby_socket], [], [], timeout) ? true : false
254
+ end
255
+
256
+ # Return the next available frame, or nil if time expired.
257
+ private def fetch_next_frame(timeout=0, start=Time.now)
258
+ frame = FFI::Frame.new
259
+
260
+ # Try fetching the next frame without a blocking call.
261
+ status = FFI.amqp_simple_wait_frame_noblock(@ptr, frame, FFI::Timeval.zero)
262
+ case status
263
+ when :ok; return frame
264
+ when :timeout; # do nothing and proceed to waiting on select below
265
+ else Util.error_check :"fetching the next frame", status
266
+ end
267
+
268
+ # Otherwise, wait for the socket to be readable and try fetching again.
269
+ return nil unless select(timeout, start)
270
+ Util.error_check :"fetching the next frame",
271
+ FFI.amqp_simple_wait_frame(@ptr, frame)
272
+
273
+ frame
274
+ end
275
+
276
+ # Fetch the next one or more frames to form the next discrete event,
277
+ # returning the event as a Hash, or nil if time expired.
278
+ private def fetch_next_event(timeout=0, start=Time.now)
279
+ frame = fetch_next_frame(timeout, start)
280
+ return unless frame
281
+ event = frame.as_method_to_h(false)
282
+ return event unless FFI::Method.has_content?(event.fetch(:method))
283
+
284
+ frame = fetch_next_frame(timeout, start)
285
+ return unless frame
286
+ event.merge!(frame.as_header_to_h)
287
+
288
+ body = ""
289
+ while body.size < event.fetch(:body_size)
290
+ frame = fetch_next_frame(timeout, start)
291
+ return unless frame
292
+ body.concat frame.as_body_to_s
293
+ end
294
+
295
+ event[:body] = body
296
+ event
297
+ end
298
+
299
+ # Execute the handler for this type of event, if any
300
+ private def handle_incoming_event(event)
301
+ if (handlers = @event_handlers[event.fetch(:channel)])
302
+ if (handler = (handlers[event.fetch(:method)]))
303
+ handler.call(event)
304
+ end
305
+ end
306
+ end
307
+
308
+ # Store the event in short-term storage for retrieval by fetch_response.
309
+ # If another event is received with the same method name, it will
310
+ # overwrite this one - fetch_response gets the latest or next by method.
311
+ # Raises an exception if the incoming event is an error condition.
312
+ private def store_incoming_event(event)
313
+ method = event.fetch(:method)
314
+
315
+ case method
316
+ when :channel_close
317
+ raise_if_server_error!(event)
318
+ when :connection_close
319
+ raise_if_server_error!(event)
320
+ else
321
+ @incoming_events[event.fetch(:channel)][method] = event
322
+ end
323
+ end
324
+
325
+ # Raise an exception if the incoming event is an error condition.
326
+ # Also takes action to reopen the channel or close the connection.
327
+ private def raise_if_server_error!(event)
328
+ if (exc = ServerError.from(event))
329
+ if exc.is_a?(ServerError::ChannelError)
330
+ reopen_channel(event.fetch(:channel)) # recover by reopening the channel
331
+ elsif exc.is_a?(ServerError::ConnectionError)
332
+ close # can't recover here - close and let the user recover manually
333
+ end
334
+ raise exc
335
+ end
336
+ end
337
+
338
+ private def fetch_events(timeout=protocol_timeout, start=Time.now)
98
339
  raise DestroyedError unless @ptr
99
340
 
100
- FFI.amqp_channel_open(@ptr, number)
101
- rpc_check :"opening channel", FFI.amqp_get_rpc_reply(@ptr)
341
+ FFI.amqp_maybe_release_buffers(@ptr)
342
+
343
+ while (event = fetch_next_event(timeout, start))
344
+ handle_incoming_event(event)
345
+ store_incoming_event(event)
346
+ break if @breaking
347
+ end
102
348
  end
103
349
 
104
- private def rpc_check action, res
105
- case res[:reply_type]
106
- when :library_exception; Util.error_check action, res[:library_error]
107
- when :server_exception; raise NotImplementedError
108
- else res
350
+ private def fetch_response_internal(channel, methods, timeout=protocol_timeout, start=Time.now)
351
+ raise DestroyedError unless @ptr
352
+
353
+ methods.each { |method|
354
+ found = @incoming_events[channel].delete(method)
355
+ return found if found
356
+ }
357
+
358
+ FFI.amqp_maybe_release_buffers_on_channel(@ptr, channel)
359
+
360
+ while (event = fetch_next_event(timeout, start))
361
+ handle_incoming_event(event)
362
+ return event if channel == event.fetch(:channel) \
363
+ && methods.include?(event.fetch(:method))
364
+ store_incoming_event(event)
109
365
  end
366
+
367
+ raise FFI::Error::Timeout, "waiting for response"
368
+ end
369
+
370
+ private def send_request_internal(channel_id, method, properties={})
371
+ raise DestroyedError unless @ptr
372
+
373
+ req = FFI::Method.lookup_class(method).new.apply(properties)
374
+ status = FFI.amqp_send_method(@ptr, channel_id, method, req.pointer)
375
+
376
+ req.free!
377
+ status
110
378
  end
111
379
  end
112
380
  end