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 +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
|