rabbitmq 0.2.5 → 1.0.0.pre.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -2
- data/lib/rabbitmq.rb +1 -1
- data/lib/rabbitmq/channel.rb +117 -81
- data/lib/rabbitmq/client.rb +332 -0
- data/lib/rabbitmq/{connection/transport.rb → client/connection.rb} +32 -11
- data/lib/rabbitmq/ffi.rb +486 -483
- data/lib/rabbitmq/util.rb +6 -3
- metadata +6 -7
- data/lib/rabbitmq/connection.rb +0 -382
- data/lib/rabbitmq/connection/channel_manager.rb +0 -61
- data/lib/rabbitmq/connection/event_manager.rb +0 -55
data/lib/rabbitmq/util.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
|
2
2
|
module RabbitMQ
|
3
|
-
|
4
|
-
|
3
|
+
|
4
|
+
# Helper functions for this library.
|
5
|
+
# @api private
|
6
|
+
module Util
|
7
|
+
module_function
|
5
8
|
|
6
9
|
def const_name(lowercase_name)
|
7
10
|
lowercase_name.to_s.gsub(/((?:\A\w)|(?:_\w))/) { |x| x[-1].upcase }
|
@@ -47,6 +50,6 @@ module RabbitMQ
|
|
47
50
|
|
48
51
|
result.merge(overrides)
|
49
52
|
end
|
50
|
-
|
51
53
|
end
|
54
|
+
|
52
55
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabbitmq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.pre.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe McIlvain
|
@@ -126,10 +126,8 @@ files:
|
|
126
126
|
- ext/rabbitmq/Rakefile
|
127
127
|
- lib/rabbitmq.rb
|
128
128
|
- lib/rabbitmq/channel.rb
|
129
|
-
- lib/rabbitmq/
|
130
|
-
- lib/rabbitmq/connection
|
131
|
-
- lib/rabbitmq/connection/event_manager.rb
|
132
|
-
- lib/rabbitmq/connection/transport.rb
|
129
|
+
- lib/rabbitmq/client.rb
|
130
|
+
- lib/rabbitmq/client/connection.rb
|
133
131
|
- lib/rabbitmq/ffi.rb
|
134
132
|
- lib/rabbitmq/ffi/error.rb
|
135
133
|
- lib/rabbitmq/ffi/ext.rb
|
@@ -224,9 +222,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
224
222
|
version: '0'
|
225
223
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
226
224
|
requirements:
|
227
|
-
- - "
|
225
|
+
- - ">"
|
228
226
|
- !ruby/object:Gem::Version
|
229
|
-
version:
|
227
|
+
version: 1.3.1
|
230
228
|
requirements: []
|
231
229
|
rubyforge_project:
|
232
230
|
rubygems_version: 2.2.2
|
@@ -234,3 +232,4 @@ signing_key:
|
|
234
232
|
specification_version: 4
|
235
233
|
summary: rabbitmq
|
236
234
|
test_files: []
|
235
|
+
has_rdoc:
|
data/lib/rabbitmq/connection.rb
DELETED
@@ -1,382 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'socket'
|
3
|
-
|
4
|
-
module RabbitMQ
|
5
|
-
class Connection
|
6
|
-
def initialize *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
|
-
|
15
|
-
@frame = FFI::Frame.new
|
16
|
-
|
17
|
-
create_socket!
|
18
|
-
|
19
|
-
@finalizer = self.class.send :create_finalizer_for, @ptr
|
20
|
-
ObjectSpace.define_finalizer self, @finalizer
|
21
|
-
end
|
22
|
-
|
23
|
-
def destroy
|
24
|
-
if @finalizer
|
25
|
-
@finalizer.call
|
26
|
-
ObjectSpace.undefine_finalizer self
|
27
|
-
end
|
28
|
-
@ptr = @socket = @finalizer = nil
|
29
|
-
end
|
30
|
-
|
31
|
-
class DestroyedError < RuntimeError; end
|
32
|
-
|
33
|
-
# @private
|
34
|
-
def self.create_finalizer_for(ptr)
|
35
|
-
Proc.new do
|
36
|
-
FFI.amqp_connection_close(ptr, 200)
|
37
|
-
FFI.amqp_destroy_connection(ptr)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def user; @info[:user]; end
|
42
|
-
def password; @info[:password]; end
|
43
|
-
def host; @info[:host]; end
|
44
|
-
def vhost; @info[:vhost]; end
|
45
|
-
def port; @info[:port]; end
|
46
|
-
def ssl?; @info[:ssl]; end
|
47
|
-
|
48
|
-
def protocol_timeout
|
49
|
-
@protocol_timeout ||= 30 # seconds
|
50
|
-
end
|
51
|
-
attr_writer :protocol_timeout
|
52
|
-
|
53
|
-
def max_channels
|
54
|
-
@max_channels ||= FFI::CHANNEL_MAX_ID
|
55
|
-
end
|
56
|
-
attr_writer :max_channels
|
57
|
-
|
58
|
-
def max_frame_size
|
59
|
-
@max_frame_size ||= 131072
|
60
|
-
end
|
61
|
-
attr_writer :max_frame_size
|
62
|
-
|
63
|
-
def heartbeat_interval; 0; end # not fully implemented in librabbitmq
|
64
|
-
|
65
|
-
def start
|
66
|
-
close # Close if already open
|
67
|
-
connect_socket!
|
68
|
-
login!
|
69
|
-
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
def close
|
74
|
-
raise DestroyedError unless @ptr
|
75
|
-
FFI.amqp_connection_close(@ptr, 200)
|
76
|
-
|
77
|
-
release_all_channels
|
78
|
-
|
79
|
-
self
|
80
|
-
end
|
81
|
-
|
82
|
-
# Send a request on the given channel with the given type and properties.
|
83
|
-
#
|
84
|
-
# @param channel_id [Integer] The channel number to send on.
|
85
|
-
# @param method [Symbol] The type of protocol method to send.
|
86
|
-
# @param properties [Hash] The properties to apply to the method.
|
87
|
-
# @raise [RabbitMQ::FFI::Error] if a library exception occurs.
|
88
|
-
#
|
89
|
-
def send_request(channel_id, method, properties={})
|
90
|
-
Util.error_check :"sending a request",
|
91
|
-
send_request_internal(Integer(channel_id), method.to_sym, properties)
|
92
|
-
|
93
|
-
nil
|
94
|
-
end
|
95
|
-
|
96
|
-
# Wait for a specific response on the given channel of the given type
|
97
|
-
# and return the event data for the response when it is received.
|
98
|
-
# Any other events received will be processed or stored internally.
|
99
|
-
#
|
100
|
-
# @param channel_id [Integer] The channel number to watch for.
|
101
|
-
# @param method [Symbol,Array<Symbol>] The protocol method(s) to watch for.
|
102
|
-
# @param timeout [Float] The maximum time to wait for a response in seconds;
|
103
|
-
# uses the value of {#protocol_timeout} by default.
|
104
|
-
# @raise [RabbitMQ::ServerError] if any error event is received.
|
105
|
-
# @raise [RabbitMQ::FFI::Error::Timeout] if no event is received.
|
106
|
-
# @raise [RabbitMQ::FFI::Error] if a library exception occurs.
|
107
|
-
# @return [Hash] the response data received.
|
108
|
-
#
|
109
|
-
def fetch_response(channel_id, method, timeout=protocol_timeout)
|
110
|
-
methods = Array(method).map(&:to_sym)
|
111
|
-
fetch_response_internal(Integer(channel_id), methods, Float(timeout))
|
112
|
-
end
|
113
|
-
|
114
|
-
# Register a handler for events on the given channel of the given type.
|
115
|
-
#
|
116
|
-
# @param channel_id [Integer] The channel number to watch for.
|
117
|
-
# @param method [Symbol] The type of protocol method to watch for.
|
118
|
-
# @param callable [#call,nil] The callable handler if no block is given.
|
119
|
-
# @param block [Proc,nil] The handler block to register.
|
120
|
-
# @return [Proc,#call] The given block or callable.
|
121
|
-
# @yieldparam event [Hash] The event passed to the handler.
|
122
|
-
#
|
123
|
-
def on_event(channel_id, method, callable=nil, &block)
|
124
|
-
handler = block || callable
|
125
|
-
raise ArgumentError, "expected block or callable as event handler" \
|
126
|
-
unless handler.respond_to?(:call)
|
127
|
-
|
128
|
-
@event_handlers[Integer(channel_id)][method.to_sym] = handler
|
129
|
-
handler
|
130
|
-
end
|
131
|
-
|
132
|
-
# Fetch and handle events in a loop that blocks the calling thread.
|
133
|
-
# The loop will continue until the {#break!} method is called from within
|
134
|
-
# an event handler, or until the given timeout duration has elapsed.
|
135
|
-
#
|
136
|
-
# @param timeout [Float] the maximum time to run the loop, in seconds;
|
137
|
-
# if none is given, the loop will block indefinitely or until {#break!}.
|
138
|
-
#
|
139
|
-
def run_loop!(timeout=nil)
|
140
|
-
timeout = Float(timeout) if timeout
|
141
|
-
@breaking = false
|
142
|
-
fetch_events(timeout)
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
|
146
|
-
# Stop iterating from within an execution of the {#run_loop!} method.
|
147
|
-
# Call this method only from within an event handler.
|
148
|
-
# It will take effect only after the handler finishes running.
|
149
|
-
#
|
150
|
-
# @return [nil]
|
151
|
-
#
|
152
|
-
def break!
|
153
|
-
@breaking = true
|
154
|
-
nil
|
155
|
-
end
|
156
|
-
|
157
|
-
def channel(id=nil)
|
158
|
-
Channel.new(self, allocate_channel(id), pre_allocated: true)
|
159
|
-
end
|
160
|
-
|
161
|
-
private def ptr
|
162
|
-
raise DestroyedError unless @ptr
|
163
|
-
@ptr
|
164
|
-
end
|
165
|
-
|
166
|
-
private def create_socket!
|
167
|
-
raise DestroyedError unless @ptr
|
168
|
-
|
169
|
-
@socket = FFI.amqp_tcp_socket_new(@ptr)
|
170
|
-
Util.null_check :"creating a socket", @socket
|
171
|
-
end
|
172
|
-
|
173
|
-
private def connect_socket!
|
174
|
-
raise DestroyedError unless @ptr
|
175
|
-
raise NotImplementedError if ssl?
|
176
|
-
|
177
|
-
create_socket!
|
178
|
-
Util.error_check :"opening a socket",
|
179
|
-
FFI.amqp_socket_open(@socket, host, port)
|
180
|
-
|
181
|
-
@ruby_socket = Socket.for_fd(FFI.amqp_get_sockfd(@ptr))
|
182
|
-
@ruby_socket.autoclose = false
|
183
|
-
end
|
184
|
-
|
185
|
-
private def login!
|
186
|
-
raise DestroyedError unless @ptr
|
187
|
-
|
188
|
-
res = FFI.amqp_login(@ptr, vhost, max_channels, max_frame_size,
|
189
|
-
heartbeat_interval, :plain, :string, user, :string, password)
|
190
|
-
|
191
|
-
case res[:reply_type]
|
192
|
-
when :library_exception; Util.error_check :"logging in", res[:library_error]
|
193
|
-
when :server_exception; raise NotImplementedError
|
194
|
-
end
|
195
|
-
|
196
|
-
@server_properties = FFI::Table.new(FFI.amqp_get_server_properties(@ptr)).to_h
|
197
|
-
end
|
198
|
-
|
199
|
-
private def open_channel(id)
|
200
|
-
Util.error_check :"opening a new channel",
|
201
|
-
send_request_internal(id, :channel_open)
|
202
|
-
|
203
|
-
fetch_response(id, :channel_open_ok)
|
204
|
-
end
|
205
|
-
|
206
|
-
private def reopen_channel(id)
|
207
|
-
Util.error_check :"acknowledging server-initated channel closure",
|
208
|
-
send_request_internal(id, :channel_close_ok)
|
209
|
-
|
210
|
-
Util.error_check :"reopening channel after server-initated closure",
|
211
|
-
send_request_internal(id, :channel_open)
|
212
|
-
|
213
|
-
fetch_response(id, :channel_open_ok)
|
214
|
-
end
|
215
|
-
|
216
|
-
private def allocate_channel(id=nil)
|
217
|
-
if id
|
218
|
-
raise ArgumentError, "channel #{id} is already in use" if @open_channels[id]
|
219
|
-
elsif @released_channels.empty?
|
220
|
-
id = (@open_channels.keys.sort.last || 0) + 1
|
221
|
-
else
|
222
|
-
id = @released_channels.keys.first
|
223
|
-
end
|
224
|
-
raise ArgumentError, "channel #{id} is too high" if id > max_channels
|
225
|
-
|
226
|
-
already_open = @released_channels.delete(id)
|
227
|
-
open_channel(id) unless already_open
|
228
|
-
|
229
|
-
@open_channels[id] = true
|
230
|
-
@event_handlers[id] ||= {}
|
231
|
-
|
232
|
-
id
|
233
|
-
end
|
234
|
-
|
235
|
-
private def release_channel(id)
|
236
|
-
@open_channels.delete(id)
|
237
|
-
@event_handlers.delete(id)
|
238
|
-
@released_channels[id] = true
|
239
|
-
end
|
240
|
-
|
241
|
-
private def release_all_channels
|
242
|
-
@open_channels.clear
|
243
|
-
@event_handlers.clear
|
244
|
-
@released_channels.clear
|
245
|
-
end
|
246
|
-
|
247
|
-
# Block until there is readable data on the internal ruby socket,
|
248
|
-
# returning true if there is readable data, or false if time expired.
|
249
|
-
private def select(timeout=0, start=Time.now)
|
250
|
-
if timeout
|
251
|
-
timeout = timeout - (start-Time.now)
|
252
|
-
timeout = 0 if timeout < 0
|
253
|
-
end
|
254
|
-
|
255
|
-
IO.select([@ruby_socket], [], [], timeout) ? true : false
|
256
|
-
end
|
257
|
-
|
258
|
-
# Return the next available frame, or nil if time expired.
|
259
|
-
private def fetch_next_frame(timeout=0, start=Time.now)
|
260
|
-
frame = @frame
|
261
|
-
|
262
|
-
# Try fetching the next frame without a blocking call.
|
263
|
-
status = FFI.amqp_simple_wait_frame_noblock(@ptr, frame, FFI::Timeval.zero)
|
264
|
-
case status
|
265
|
-
when :ok; return frame
|
266
|
-
when :timeout; # do nothing and proceed to waiting on select below
|
267
|
-
else Util.error_check :"fetching the next frame", status
|
268
|
-
end
|
269
|
-
|
270
|
-
# Otherwise, wait for the socket to be readable and try fetching again.
|
271
|
-
return nil unless select(timeout, start)
|
272
|
-
Util.error_check :"fetching the next frame",
|
273
|
-
FFI.amqp_simple_wait_frame(@ptr, frame)
|
274
|
-
|
275
|
-
frame
|
276
|
-
end
|
277
|
-
|
278
|
-
# Fetch the next one or more frames to form the next discrete event,
|
279
|
-
# returning the event as a Hash, or nil if time expired.
|
280
|
-
private def fetch_next_event(timeout=0, start=Time.now)
|
281
|
-
frame = fetch_next_frame(timeout, start)
|
282
|
-
return unless frame
|
283
|
-
event = frame.as_method_to_h(false)
|
284
|
-
return event unless FFI::Method.has_content?(event.fetch(:method))
|
285
|
-
|
286
|
-
frame = fetch_next_frame(timeout, start)
|
287
|
-
return unless frame
|
288
|
-
event.merge!(frame.as_header_to_h)
|
289
|
-
|
290
|
-
body = ""
|
291
|
-
while body.size < event.fetch(:body_size)
|
292
|
-
frame = fetch_next_frame(timeout, start)
|
293
|
-
return unless frame
|
294
|
-
body.concat frame.as_body_to_s
|
295
|
-
end
|
296
|
-
|
297
|
-
event[:body] = body
|
298
|
-
event
|
299
|
-
end
|
300
|
-
|
301
|
-
# Execute the handler for this type of event, if any
|
302
|
-
private def handle_incoming_event(event)
|
303
|
-
if (handlers = @event_handlers[event.fetch(:channel)])
|
304
|
-
if (handler = (handlers[event.fetch(:method)]))
|
305
|
-
handler.call(event)
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
# Store the event in short-term storage for retrieval by fetch_response.
|
311
|
-
# If another event is received with the same method name, it will
|
312
|
-
# overwrite this one - fetch_response gets the latest or next by method.
|
313
|
-
# Raises an exception if the incoming event is an error condition.
|
314
|
-
private def store_incoming_event(event)
|
315
|
-
method = event.fetch(:method)
|
316
|
-
|
317
|
-
case method
|
318
|
-
when :channel_close
|
319
|
-
raise_if_server_error!(event)
|
320
|
-
when :connection_close
|
321
|
-
raise_if_server_error!(event)
|
322
|
-
else
|
323
|
-
@incoming_events[event.fetch(:channel)][method] = event
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
# Raise an exception if the incoming event is an error condition.
|
328
|
-
# Also takes action to reopen the channel or close the connection.
|
329
|
-
private def raise_if_server_error!(event)
|
330
|
-
if (exc = ServerError.from(event))
|
331
|
-
if exc.is_a?(ServerError::ChannelError)
|
332
|
-
reopen_channel(event.fetch(:channel)) # recover by reopening the channel
|
333
|
-
elsif exc.is_a?(ServerError::ConnectionError)
|
334
|
-
close # can't recover here - close and let the user recover manually
|
335
|
-
end
|
336
|
-
raise exc
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
private def fetch_events(timeout=protocol_timeout, start=Time.now)
|
341
|
-
raise DestroyedError unless @ptr
|
342
|
-
|
343
|
-
FFI.amqp_maybe_release_buffers(@ptr)
|
344
|
-
|
345
|
-
while (event = fetch_next_event(timeout, start))
|
346
|
-
handle_incoming_event(event)
|
347
|
-
store_incoming_event(event)
|
348
|
-
break if @breaking
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
|
-
private def fetch_response_internal(channel, methods, timeout=protocol_timeout, start=Time.now)
|
353
|
-
raise DestroyedError unless @ptr
|
354
|
-
|
355
|
-
methods.each { |method|
|
356
|
-
found = @incoming_events[channel].delete(method)
|
357
|
-
return found if found
|
358
|
-
}
|
359
|
-
|
360
|
-
FFI.amqp_maybe_release_buffers_on_channel(@ptr, channel)
|
361
|
-
|
362
|
-
while (event = fetch_next_event(timeout, start))
|
363
|
-
handle_incoming_event(event)
|
364
|
-
return event if channel == event.fetch(:channel) \
|
365
|
-
&& methods.include?(event.fetch(:method))
|
366
|
-
store_incoming_event(event)
|
367
|
-
end
|
368
|
-
|
369
|
-
raise FFI::Error::Timeout, "waiting for response"
|
370
|
-
end
|
371
|
-
|
372
|
-
private def send_request_internal(channel_id, method, properties={})
|
373
|
-
raise DestroyedError unless @ptr
|
374
|
-
|
375
|
-
req = FFI::Method.lookup_class(method).new.apply(properties)
|
376
|
-
status = FFI.amqp_send_method(@ptr, channel_id, method, req.pointer)
|
377
|
-
|
378
|
-
req.free!
|
379
|
-
status
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
|
2
|
-
module RabbitMQ
|
3
|
-
class Connection
|
4
|
-
|
5
|
-
class ChannelManager
|
6
|
-
def initialize(connection)
|
7
|
-
@connection = connection
|
8
|
-
@open_channels = {}
|
9
|
-
@released_channels = {}
|
10
|
-
end
|
11
|
-
|
12
|
-
def allocate_channel(id=nil)
|
13
|
-
if id
|
14
|
-
raise ArgumentError, "channel #{id} is already in use" if @open_channels[id]
|
15
|
-
elsif @released_channels.empty?
|
16
|
-
id = (@open_channels.keys.sort.last || 0) + 1
|
17
|
-
else
|
18
|
-
id = @released_channels.keys.first
|
19
|
-
end
|
20
|
-
raise ArgumentError, "channel #{id} is too high" if id > @connection.max_channels
|
21
|
-
|
22
|
-
already_open = @released_channels.delete(id)
|
23
|
-
request_channel_open(id) unless already_open
|
24
|
-
|
25
|
-
@open_channels[id] = true
|
26
|
-
@event_handlers[id] ||= {}
|
27
|
-
|
28
|
-
id
|
29
|
-
end
|
30
|
-
|
31
|
-
def release_channel(id)
|
32
|
-
@open_channels.delete(id)
|
33
|
-
@released_channels[id] = true
|
34
|
-
end
|
35
|
-
|
36
|
-
def release_all_channels
|
37
|
-
@open_channels.clear
|
38
|
-
@released_channels.clear
|
39
|
-
end
|
40
|
-
|
41
|
-
def request_channel_open(id)
|
42
|
-
Util.error_check :"opening a new channel",
|
43
|
-
@connection.send_request_internal(id, :channel_open)
|
44
|
-
|
45
|
-
@connection.fetch_response(id, :channel_open_ok)
|
46
|
-
end
|
47
|
-
|
48
|
-
def request_channel_reopen(id)
|
49
|
-
Util.error_check :"acknowledging server-initated channel closure",
|
50
|
-
@connection.send_request_internal(id, :channel_close_ok)
|
51
|
-
|
52
|
-
Util.error_check :"reopening channel after server-initated closure",
|
53
|
-
@connection.send_request_internal(id, :channel_open)
|
54
|
-
|
55
|
-
@connection.fetch_response(id, :channel_open_ok)
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|