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 +4 -4
- data/ext/rabbitmq/Rakefile +1 -1
- data/lib/rabbitmq.rb +4 -1
- data/lib/rabbitmq/channel.rb +261 -0
- data/lib/rabbitmq/connection.rb +298 -30
- data/lib/rabbitmq/ffi.rb +110 -99
- data/lib/rabbitmq/ffi/error.rb +2 -4
- data/lib/rabbitmq/ffi/ext.rb +232 -23
- data/lib/rabbitmq/server_error.rb +62 -0
- data/lib/rabbitmq/util.rb +26 -9
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6084d87d4d4379929fbae34f8a6d7e8f979f176
|
4
|
+
data.tar.gz: 6312eaee4b20b758986c03fa8e448f763e03cb61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0de8d7059d8117341d3605ce4f58463fb912dc581572821cd9b3bcc80f82b3a5cb0ecfc5c3540dae0c6aeea1071027e4d6bc366ae651e04a76d9c60b788e836f
|
7
|
+
data.tar.gz: e09ce58387e226cd673b51a32934cb8456ba9eaddb00031cafddb7fafa84ea8acbd5657789645eb3388954c24072cf6a25b8e564f38071a7b3a60f2c91e0961d
|
data/ext/rabbitmq/Rakefile
CHANGED
@@ -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/
|
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
|
data/lib/rabbitmq/connection.rb
CHANGED
@@ -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
|
6
|
-
|
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
|
39
|
-
|
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
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
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.
|
101
|
-
|
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
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|