totty-amqp 0.6.7.1.totty
Sign up to get free protection for your applications and to get access to all the features.
- data/README +143 -0
- data/Rakefile +20 -0
- data/amqp.todo +32 -0
- data/doc/EXAMPLE_01_PINGPONG +2 -0
- data/doc/EXAMPLE_02_CLOCK +2 -0
- data/doc/EXAMPLE_03_STOCKS +2 -0
- data/doc/EXAMPLE_04_MULTICLOCK +2 -0
- data/doc/EXAMPLE_05_ACK +2 -0
- data/doc/EXAMPLE_05_POP +2 -0
- data/doc/EXAMPLE_06_HASHTABLE +2 -0
- data/examples/amqp/simple.rb +79 -0
- data/examples/mq/ack.rb +45 -0
- data/examples/mq/clock.rb +56 -0
- data/examples/mq/hashtable.rb +52 -0
- data/examples/mq/internal.rb +49 -0
- data/examples/mq/logger.rb +88 -0
- data/examples/mq/multiclock.rb +49 -0
- data/examples/mq/pingpong.rb +45 -0
- data/examples/mq/pop.rb +43 -0
- data/examples/mq/primes-simple.rb +19 -0
- data/examples/mq/primes.rb +99 -0
- data/examples/mq/stocks.rb +58 -0
- data/lib/amqp/buffer.rb +395 -0
- data/lib/amqp/client.rb +210 -0
- data/lib/amqp/frame.rb +124 -0
- data/lib/amqp/protocol.rb +212 -0
- data/lib/amqp/server.rb +99 -0
- data/lib/amqp/spec.rb +832 -0
- data/lib/amqp/version.rb +3 -0
- data/lib/amqp.rb +152 -0
- data/lib/ext/blankslate.rb +7 -0
- data/lib/ext/em.rb +8 -0
- data/lib/ext/emfork.rb +69 -0
- data/lib/mq/exchange.rb +314 -0
- data/lib/mq/header.rb +33 -0
- data/lib/mq/logger.rb +89 -0
- data/lib/mq/queue.rb +455 -0
- data/lib/mq/rpc.rb +100 -0
- data/lib/mq.rb +877 -0
- data/old/README +30 -0
- data/old/Rakefile +12 -0
- data/old/amqp-0.8.json +606 -0
- data/old/amqp_spec.rb +796 -0
- data/old/amqpc.rb +695 -0
- data/old/codegen.rb +148 -0
- data/protocol/amqp-0.8.json +617 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +173 -0
- data/protocol/doc.txt +281 -0
- data/research/api.rb +88 -0
- data/research/primes-forked.rb +63 -0
- data/research/primes-processes.rb +135 -0
- data/research/primes-threaded.rb +49 -0
- data/totty-amqp.gemspec +87 -0
- metadata +142 -0
data/lib/mq/queue.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
class MQ
|
2
|
+
class Queue
|
3
|
+
include AMQP
|
4
|
+
|
5
|
+
# Queues store and forward messages. Queues can be configured in the server
|
6
|
+
# or created at runtime. Queues must be attached to at least one exchange
|
7
|
+
# in order to receive messages from publishers.
|
8
|
+
#
|
9
|
+
# Like an Exchange, queue names starting with 'amq.' are reserved for
|
10
|
+
# internal use. Attempts to create queue names in violation of this
|
11
|
+
# reservation will raise MQ:Error (ACCESS_REFUSED).
|
12
|
+
#
|
13
|
+
# When a queue is created without a name, the server will generate a
|
14
|
+
# unique name internally (not currently supported in this library).
|
15
|
+
#
|
16
|
+
# == Options
|
17
|
+
# * :passive => true | false (default false)
|
18
|
+
# If set, the server will not create the exchange if it does not
|
19
|
+
# already exist. The client can use this to check whether an exchange
|
20
|
+
# exists without modifying the server state.
|
21
|
+
#
|
22
|
+
# * :durable => true | false (default false)
|
23
|
+
# If set when creating a new queue, the queue will be marked as
|
24
|
+
# durable. Durable queues remain active when a server restarts.
|
25
|
+
# Non-durable queues (transient queues) are purged if/when a
|
26
|
+
# server restarts. Note that durable queues do not necessarily
|
27
|
+
# hold persistent messages, although it does not make sense to
|
28
|
+
# send persistent messages to a transient queue (though it is
|
29
|
+
# allowed).
|
30
|
+
#
|
31
|
+
# If the queue has already been declared, any redeclaration will
|
32
|
+
# ignore this setting. A queue may only be declared durable the
|
33
|
+
# first time when it is created.
|
34
|
+
#
|
35
|
+
# * :exclusive => true | false (default false)
|
36
|
+
# Exclusive queues may only be consumed from by the current connection.
|
37
|
+
# Setting the 'exclusive' flag always implies 'auto-delete'. Only a
|
38
|
+
# single consumer is allowed to remove messages from this queue.
|
39
|
+
#
|
40
|
+
# The default is a shared queue. Multiple clients may consume messages
|
41
|
+
# from this queue.
|
42
|
+
#
|
43
|
+
# Attempting to redeclare an already-declared queue as :exclusive => true
|
44
|
+
# will raise MQ:Error.
|
45
|
+
#
|
46
|
+
# * :auto_delete = true | false (default false)
|
47
|
+
# If set, the queue is deleted when all consumers have finished
|
48
|
+
# using it. Last consumer can be cancelled either explicitly or because
|
49
|
+
# its channel is closed. If there was no consumer ever on the queue, it
|
50
|
+
# won't be deleted.
|
51
|
+
#
|
52
|
+
# The server waits for a short period of time before
|
53
|
+
# determining the queue is unused to give time to the client code
|
54
|
+
# to bind an exchange to it.
|
55
|
+
#
|
56
|
+
# If the queue has been previously declared, this option is ignored
|
57
|
+
# on subsequent declarations.
|
58
|
+
#
|
59
|
+
# * :nowait => true | false (default true)
|
60
|
+
# If set, the server will not respond to the method. The client should
|
61
|
+
# not wait for a reply method. If the server could not complete the
|
62
|
+
# method it will raise a channel or connection exception.
|
63
|
+
#
|
64
|
+
def initialize mq, name, opts = {}
|
65
|
+
@mq = mq
|
66
|
+
@opts = opts
|
67
|
+
@bindings ||= {}
|
68
|
+
@mq.queues[@name = name] ||= self
|
69
|
+
@mq.callback{
|
70
|
+
@mq.send Protocol::Queue::Declare.new({ :queue => name,
|
71
|
+
:nowait => true }.merge(opts))
|
72
|
+
}
|
73
|
+
end
|
74
|
+
attr_reader :name
|
75
|
+
|
76
|
+
# This method binds a queue to an exchange. Until a queue is
|
77
|
+
# bound it will not receive any messages. In a classic messaging
|
78
|
+
# model, store-and-forward queues are bound to a dest exchange
|
79
|
+
# and subscription queues are bound to a dest_wild exchange.
|
80
|
+
#
|
81
|
+
# A valid exchange name (or reference) must be passed as the first
|
82
|
+
# parameter. Both of these are valid:
|
83
|
+
# exch = MQ.direct('foo exchange')
|
84
|
+
# queue = MQ.queue('bar queue')
|
85
|
+
# queue.bind('foo.exchange') # OR
|
86
|
+
# queue.bind(exch)
|
87
|
+
#
|
88
|
+
# It is not valid to call #bind without the +exchange+ parameter.
|
89
|
+
#
|
90
|
+
# It is unnecessary to call #bind when the exchange name and queue
|
91
|
+
# name match exactly (for +direct+ and +fanout+ exchanges only).
|
92
|
+
# There is an implicit bind which will deliver the messages from
|
93
|
+
# the exchange to the queue.
|
94
|
+
#
|
95
|
+
# == Options
|
96
|
+
# * :key => 'some string'
|
97
|
+
# Specifies the routing key for the binding. The routing key is
|
98
|
+
# used for routing messages depending on the exchange configuration.
|
99
|
+
# Not all exchanges use a routing key - refer to the specific
|
100
|
+
# exchange documentation. If the routing key is empty and the queue
|
101
|
+
# name is empty, the routing key will be the current queue for the
|
102
|
+
# channel, which is the last declared queue.
|
103
|
+
#
|
104
|
+
# * :nowait => true | false (default true)
|
105
|
+
# If set, the server will not respond to the method. The client should
|
106
|
+
# not wait for a reply method. If the server could not complete the
|
107
|
+
# method it will raise a channel or connection exception.
|
108
|
+
#
|
109
|
+
def bind exchange, opts = {}
|
110
|
+
exchange = exchange.respond_to?(:name) ? exchange.name : exchange
|
111
|
+
@bindings[exchange] = opts
|
112
|
+
|
113
|
+
@mq.callback{
|
114
|
+
@mq.send Protocol::Queue::Bind.new({ :queue => name,
|
115
|
+
:exchange => exchange,
|
116
|
+
:routing_key => opts[:key],
|
117
|
+
:nowait => true }.merge(opts))
|
118
|
+
}
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Remove the binding between the queue and exchange. The queue will
|
123
|
+
# not receive any more messages until it is bound to another
|
124
|
+
# exchange.
|
125
|
+
#
|
126
|
+
# Due to the asynchronous nature of the protocol, it is possible for
|
127
|
+
# "in flight" messages to be received after this call completes.
|
128
|
+
# Those messages will be serviced by the last block used in a
|
129
|
+
# #subscribe or #pop call.
|
130
|
+
#
|
131
|
+
# * :nowait => true | false (default true)
|
132
|
+
# If set, the server will not respond to the method. The client should
|
133
|
+
# not wait for a reply method. If the server could not complete the
|
134
|
+
# method it will raise a channel or connection exception.
|
135
|
+
#
|
136
|
+
def unbind exchange, opts = {}
|
137
|
+
exchange = exchange.respond_to?(:name) ? exchange.name : exchange
|
138
|
+
@bindings.delete exchange
|
139
|
+
|
140
|
+
@mq.callback{
|
141
|
+
@mq.send Protocol::Queue::Unbind.new({ :queue => name,
|
142
|
+
:exchange => exchange,
|
143
|
+
:routing_key => opts[:key],
|
144
|
+
:nowait => true }.merge(opts))
|
145
|
+
}
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# This method deletes a queue. When a queue is deleted any pending
|
150
|
+
# messages are sent to a dead-letter queue if this is defined in the
|
151
|
+
# server configuration, and all consumers on the queue are cancelled.
|
152
|
+
#
|
153
|
+
# == Options
|
154
|
+
# * :if_unused => true | false (default false)
|
155
|
+
# If set, the server will only delete the queue if it has no
|
156
|
+
# consumers. If the queue has consumers the server does does not
|
157
|
+
# delete it but raises a channel exception instead.
|
158
|
+
#
|
159
|
+
# * :if_empty => true | false (default false)
|
160
|
+
# If set, the server will only delete the queue if it has no
|
161
|
+
# messages. If the queue is not empty the server raises a channel
|
162
|
+
# exception.
|
163
|
+
#
|
164
|
+
# * :nowait => true | false (default true)
|
165
|
+
# If set, the server will not respond to the method. The client should
|
166
|
+
# not wait for a reply method. If the server could not complete the
|
167
|
+
# method it will raise a channel or connection exception.
|
168
|
+
#
|
169
|
+
def delete opts = {}
|
170
|
+
@mq.callback{
|
171
|
+
@mq.send Protocol::Queue::Delete.new({ :queue => name,
|
172
|
+
:nowait => true }.merge(opts))
|
173
|
+
}
|
174
|
+
@mq.queues.delete @name
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# Purge all messages from the queue.
|
179
|
+
#
|
180
|
+
def purge opts = {}
|
181
|
+
@mq.callback{
|
182
|
+
@mq.send Protocol::Queue::Purge.new({ :queue => name,
|
183
|
+
:nowait => true }.merge(opts))
|
184
|
+
}
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# This method provides a direct access to the messages in a queue
|
189
|
+
# using a synchronous dialogue that is designed for specific types of
|
190
|
+
# application where synchronous functionality is more important than
|
191
|
+
# performance.
|
192
|
+
#
|
193
|
+
# The provided block is passed a single message each time pop is called.
|
194
|
+
#
|
195
|
+
# EM.run do
|
196
|
+
# exchange = MQ.direct("foo queue")
|
197
|
+
# EM.add_periodic_timer(1) do
|
198
|
+
# exchange.publish("random number #{rand(1000)}")
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# # note that #bind is never called; it is implicit because
|
202
|
+
# # the exchange and queue names match
|
203
|
+
# queue = MQ.queue('foo queue')
|
204
|
+
# queue.pop { |body| puts "received payload [#{body}]" }
|
205
|
+
#
|
206
|
+
# EM.add_periodic_timer(1) { queue.pop }
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# If the block takes 2 parameters, both the +header+ and the +body+ will
|
210
|
+
# be passed in for processing. The header object is defined by
|
211
|
+
# AMQP::Protocol::Header.
|
212
|
+
#
|
213
|
+
# EM.run do
|
214
|
+
# exchange = MQ.direct("foo queue")
|
215
|
+
# EM.add_periodic_timer(1) do
|
216
|
+
# exchange.publish("random number #{rand(1000)}")
|
217
|
+
# end
|
218
|
+
#
|
219
|
+
# queue = MQ.queue('foo queue')
|
220
|
+
# queue.pop do |header, body|
|
221
|
+
# p header
|
222
|
+
# puts "received payload [#{body}]"
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# EM.add_periodic_timer(1) { queue.pop }
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
# == Options
|
229
|
+
# * :ack => true | false (default false)
|
230
|
+
# If this field is set to false the server does not expect acknowledgments
|
231
|
+
# for messages. That is, when a message is delivered to the client
|
232
|
+
# the server automatically and silently acknowledges it on behalf
|
233
|
+
# of the client. This functionality increases performance but at
|
234
|
+
# the cost of reliability. Messages can get lost if a client dies
|
235
|
+
# before it can deliver them to the application.
|
236
|
+
#
|
237
|
+
# * :nowait => true | false (default true)
|
238
|
+
# If set, the server will not respond to the method. The client should
|
239
|
+
# not wait for a reply method. If the server could not complete the
|
240
|
+
# method it will raise a channel or connection exception.
|
241
|
+
#
|
242
|
+
def pop opts = {}, &blk
|
243
|
+
if blk
|
244
|
+
@on_pop = blk
|
245
|
+
@on_pop_opts = opts
|
246
|
+
end
|
247
|
+
|
248
|
+
@mq.callback{
|
249
|
+
@mq.get_queue{ |q|
|
250
|
+
q.push(self)
|
251
|
+
@mq.send Protocol::Basic::Get.new({ :queue => name,
|
252
|
+
:consumer_tag => name,
|
253
|
+
:no_ack => !opts[:ack],
|
254
|
+
:nowait => true }.merge(opts))
|
255
|
+
}
|
256
|
+
}
|
257
|
+
|
258
|
+
self
|
259
|
+
end
|
260
|
+
|
261
|
+
# Subscribes to asynchronous message delivery.
|
262
|
+
#
|
263
|
+
# The provided block is passed a single message each time the
|
264
|
+
# exchange matches a message to this queue.
|
265
|
+
#
|
266
|
+
# EM.run do
|
267
|
+
# exchange = MQ.direct("foo queue")
|
268
|
+
# EM.add_periodic_timer(1) do
|
269
|
+
# exchange.publish("random number #{rand(1000)}")
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# queue = MQ.queue('foo queue')
|
273
|
+
# queue.subscribe { |body| puts "received payload [#{body}]" }
|
274
|
+
# end
|
275
|
+
#
|
276
|
+
# If the block takes 2 parameters, both the +header+ and the +body+ will
|
277
|
+
# be passed in for processing. The header object is defined by
|
278
|
+
# AMQP::Protocol::Header.
|
279
|
+
#
|
280
|
+
# EM.run do
|
281
|
+
# exchange = MQ.direct("foo queue")
|
282
|
+
# EM.add_periodic_timer(1) do
|
283
|
+
# exchange.publish("random number #{rand(1000)}")
|
284
|
+
# end
|
285
|
+
#
|
286
|
+
# # note that #bind is never called; it is implicit because
|
287
|
+
# # the exchange and queue names match
|
288
|
+
# queue = MQ.queue('foo queue')
|
289
|
+
# queue.subscribe do |header, body|
|
290
|
+
# p header
|
291
|
+
# puts "received payload [#{body}]"
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# == Options
|
296
|
+
# * :ack => true | false (default false)
|
297
|
+
# If this field is set to false the server does not expect acknowledgments
|
298
|
+
# for messages. That is, when a message is delivered to the client
|
299
|
+
# the server automatically and silently acknowledges it on behalf
|
300
|
+
# of the client. This functionality increases performance but at
|
301
|
+
# the cost of reliability. Messages can get lost if a client dies
|
302
|
+
# before it can deliver them to the application.
|
303
|
+
#
|
304
|
+
# * :nowait => true | false (default true)
|
305
|
+
# If set, the server will not respond to the method. The client should
|
306
|
+
# not wait for a reply method. If the server could not complete the
|
307
|
+
# method it will raise a channel or connection exception.
|
308
|
+
#
|
309
|
+
# * :confirm => proc (default nil)
|
310
|
+
# If set, this proc will be called when the server confirms subscription
|
311
|
+
# to the queue with a ConsumeOk message. Setting this option will
|
312
|
+
# automatically set :nowait => false. This is required for the server
|
313
|
+
# to send a confirmation.
|
314
|
+
#
|
315
|
+
def subscribe opts = {}, &blk
|
316
|
+
@consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
|
317
|
+
@mq.consumers[@consumer_tag] = self
|
318
|
+
|
319
|
+
raise Error, 'already subscribed to the queue' if subscribed?
|
320
|
+
|
321
|
+
@on_msg = blk
|
322
|
+
@on_msg_opts = opts
|
323
|
+
opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
|
324
|
+
|
325
|
+
@mq.callback{
|
326
|
+
@mq.send Protocol::Basic::Consume.new({ :queue => name,
|
327
|
+
:consumer_tag => @consumer_tag,
|
328
|
+
:no_ack => !opts[:ack],
|
329
|
+
:nowait => true }.merge(opts))
|
330
|
+
}
|
331
|
+
self
|
332
|
+
end
|
333
|
+
|
334
|
+
# Removes the subscription from the queue and cancels the consumer.
|
335
|
+
# New messages will not be received by the queue. This call is similar
|
336
|
+
# in result to calling #unbind.
|
337
|
+
#
|
338
|
+
# Due to the asynchronous nature of the protocol, it is possible for
|
339
|
+
# "in flight" messages to be received after this call completes.
|
340
|
+
# Those messages will be serviced by the last block used in a
|
341
|
+
# #subscribe or #pop call.
|
342
|
+
#
|
343
|
+
# Additionally, if the queue was created with _autodelete_ set to
|
344
|
+
# true, the server will delete the queue after its wait period
|
345
|
+
# has expired unless the queue is bound to an active exchange.
|
346
|
+
#
|
347
|
+
# The method accepts a block which will be executed when the
|
348
|
+
# unsubscription request is acknowledged as complete by the server.
|
349
|
+
#
|
350
|
+
# * :nowait => true | false (default true)
|
351
|
+
# If set, the server will not respond to the method. The client should
|
352
|
+
# not wait for a reply method. If the server could not complete the
|
353
|
+
# method it will raise a channel or connection exception.
|
354
|
+
#
|
355
|
+
def unsubscribe opts = {}, &blk
|
356
|
+
@on_cancel = blk
|
357
|
+
@mq.callback{
|
358
|
+
@mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
|
359
|
+
}
|
360
|
+
self
|
361
|
+
end
|
362
|
+
|
363
|
+
def publish data, opts = {}
|
364
|
+
exchange.publish(data, opts)
|
365
|
+
end
|
366
|
+
|
367
|
+
# Boolean check to see if the current queue has already been subscribed
|
368
|
+
# to an exchange.
|
369
|
+
#
|
370
|
+
# Attempts to #subscribe multiple times to any exchange will raise an
|
371
|
+
# Exception. Only a single block at a time can be associated with any
|
372
|
+
# one queue for processing incoming messages.
|
373
|
+
#
|
374
|
+
def subscribed?
|
375
|
+
!!@on_msg
|
376
|
+
end
|
377
|
+
|
378
|
+
# Passes the message to the block passed to pop or subscribe.
|
379
|
+
#
|
380
|
+
# Performs an arity check on the block's parameters. If arity == 1,
|
381
|
+
# pass only the message body. If arity != 1, pass the headers and
|
382
|
+
# the body to the block.
|
383
|
+
#
|
384
|
+
# See AMQP::Protocol::Header for the hash properties available from
|
385
|
+
# the headers parameter. See #pop or #subscribe for a code example.
|
386
|
+
#
|
387
|
+
def receive headers, body
|
388
|
+
headers = MQ::Header.new(@mq, headers)
|
389
|
+
|
390
|
+
if cb = (@on_msg || @on_pop)
|
391
|
+
cb.call *(cb.arity == 1 ? [body] : [headers, body])
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Get the number of messages and consumers on a queue.
|
396
|
+
#
|
397
|
+
# MQ.queue('name').status{ |num_messages, num_consumers|
|
398
|
+
# puts num_messages
|
399
|
+
# }
|
400
|
+
#
|
401
|
+
def status opts = {}, &blk
|
402
|
+
@on_status = blk
|
403
|
+
@mq.callback{
|
404
|
+
@mq.send Protocol::Queue::Declare.new({ :queue => name,
|
405
|
+
:passive => true }.merge(opts))
|
406
|
+
}
|
407
|
+
self
|
408
|
+
end
|
409
|
+
|
410
|
+
def receive_status declare_ok
|
411
|
+
if @on_status
|
412
|
+
m, c = declare_ok.message_count, declare_ok.consumer_count
|
413
|
+
@on_status.call *(@on_status.arity == 1 ? [m] : [m, c])
|
414
|
+
@on_status = nil
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def confirm_subscribe
|
419
|
+
@on_confirm_subscribe.call if @on_confirm_subscribe
|
420
|
+
@on_confirm_subscribe = nil
|
421
|
+
end
|
422
|
+
|
423
|
+
def cancelled
|
424
|
+
@on_cancel.call if @on_cancel
|
425
|
+
@on_cancel = @on_msg = nil
|
426
|
+
@mq.consumers.delete @consumer_tag
|
427
|
+
@mq.queues.delete(@name) if @opts[:auto_delete]
|
428
|
+
@consumer_tag = nil
|
429
|
+
end
|
430
|
+
|
431
|
+
def reset
|
432
|
+
@deferred_status = nil
|
433
|
+
initialize @mq, @name, @opts
|
434
|
+
|
435
|
+
binds = @bindings
|
436
|
+
@bindings = {}
|
437
|
+
binds.each{|ex,opts| bind(ex, opts) }
|
438
|
+
|
439
|
+
if blk = @on_msg
|
440
|
+
@on_msg = nil
|
441
|
+
subscribe @on_msg_opts, &blk
|
442
|
+
end
|
443
|
+
|
444
|
+
if @on_pop
|
445
|
+
pop @on_pop_opts, &@on_pop
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
private
|
450
|
+
|
451
|
+
def exchange
|
452
|
+
@exchange ||= Exchange.new(@mq, :direct, '', :key => name)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
data/lib/mq/rpc.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
class MQ
|
2
|
+
# Basic RPC (remote procedure call) facility.
|
3
|
+
#
|
4
|
+
# Needs more detail and explanation.
|
5
|
+
#
|
6
|
+
# EM.run do
|
7
|
+
# server = MQ.rpc('hash table node', Hash)
|
8
|
+
#
|
9
|
+
# client = MQ.rpc('hash table node')
|
10
|
+
# client[:now] = Time.now
|
11
|
+
# client[:one] = 1
|
12
|
+
#
|
13
|
+
# client.values do |res|
|
14
|
+
# p 'client', :values => res
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# client.keys do |res|
|
18
|
+
# p 'client', :keys => res
|
19
|
+
# EM.stop_event_loop
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
class RPC < BlankSlate
|
24
|
+
# Takes a channel, queue and optional object.
|
25
|
+
#
|
26
|
+
# The optional object may be a class name, module name or object
|
27
|
+
# instance. When given a class or module name, the object is instantiated
|
28
|
+
# during this setup. The passed queue is automatically subscribed to so
|
29
|
+
# it passes all messages (and their arguments) to the object.
|
30
|
+
#
|
31
|
+
# Marshalling and unmarshalling the objects is handled internally. This
|
32
|
+
# marshalling is subject to the same restrictions as defined in the
|
33
|
+
# Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
|
34
|
+
# library. See that documentation for further reference.
|
35
|
+
#
|
36
|
+
# When the optional object is not passed, the returned rpc reference is
|
37
|
+
# used to send messages and arguments to the queue. See #method_missing
|
38
|
+
# which does all of the heavy lifting with the proxy. Some client
|
39
|
+
# elsewhere must call this method *with* the optional block so that
|
40
|
+
# there is a valid destination. Failure to do so will just enqueue
|
41
|
+
# marshalled messages that are never consumed.
|
42
|
+
#
|
43
|
+
def initialize mq, queue, obj = nil
|
44
|
+
@mq = mq
|
45
|
+
@mq.rpcs[queue] ||= self
|
46
|
+
|
47
|
+
if obj
|
48
|
+
@obj = case obj
|
49
|
+
when ::Class
|
50
|
+
obj.new
|
51
|
+
when ::Module
|
52
|
+
(::Class.new do include(obj) end).new
|
53
|
+
else
|
54
|
+
obj
|
55
|
+
end
|
56
|
+
|
57
|
+
@mq.queue(queue).subscribe(:ack=>true){ |info, request|
|
58
|
+
method, *args = ::Marshal.load(request)
|
59
|
+
ret = @obj.__send__(method, *args)
|
60
|
+
|
61
|
+
info.ack
|
62
|
+
|
63
|
+
if info.reply_to
|
64
|
+
@mq.queue(info.reply_to).publish(::Marshal.dump(ret), :key => info.reply_to, :message_id => info.message_id)
|
65
|
+
end
|
66
|
+
}
|
67
|
+
else
|
68
|
+
@callbacks ||= {}
|
69
|
+
# XXX implement and use queue(nil)
|
70
|
+
@queue = @mq.queue(@name = "random identifier #{::Kernel.rand(999_999_999_999)}", :auto_delete => true).subscribe{|info, msg|
|
71
|
+
if blk = @callbacks.delete(info.message_id)
|
72
|
+
blk.call ::Marshal.load(msg)
|
73
|
+
end
|
74
|
+
}
|
75
|
+
@remote = @mq.queue(queue)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calling MQ.rpc(*args) returns a proxy object without any methods beyond
|
80
|
+
# those in Object. All calls to the proxy are handled by #method_missing which
|
81
|
+
# works to marshal and unmarshal all method calls and their arguments.
|
82
|
+
#
|
83
|
+
# EM.run do
|
84
|
+
# server = MQ.rpc('hash table node', Hash)
|
85
|
+
# client = MQ.rpc('hash table node')
|
86
|
+
#
|
87
|
+
# # calls #method_missing on #[] which marshals the method name and
|
88
|
+
# # arguments to publish them to the remote
|
89
|
+
# client[:now] = Time.now
|
90
|
+
# ....
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
def method_missing meth, *args, &blk
|
94
|
+
# XXX use uuids instead
|
95
|
+
message_id = "random message id #{::Kernel.rand(999_999_999_999)}"
|
96
|
+
@callbacks[message_id] = blk if blk
|
97
|
+
@remote.publish(::Marshal.dump([meth, *args]), :reply_to => blk ? @name : nil, :message_id => message_id)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|