rabbitmq 0.1.1 → 0.2.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: 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