mqrpc 0.0.1 → 0.0.3
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.
- data/lib/mqrpc/agent.rb +62 -25
- data/lib/mqrpc/config.rb +2 -0
- data/lib/mqrpc/message.rb +32 -37
- data/lib/mqrpc/messages/ping.rb +1 -1
- data/lib/mqrpc/operation.rb +5 -6
- data/lib/mqrpc/sizedhash.rb +97 -0
- metadata +3 -2
data/lib/mqrpc/agent.rb
CHANGED
@@ -3,6 +3,7 @@ require 'amqp'
|
|
3
3
|
require 'mq'
|
4
4
|
require 'mqrpc/logger'
|
5
5
|
require 'mqrpc/operation'
|
6
|
+
require 'mqrpc/sizedhash'
|
6
7
|
require 'thread'
|
7
8
|
require 'uuid'
|
8
9
|
|
@@ -27,6 +28,7 @@ module MQRPC
|
|
27
28
|
# TODO: document this class
|
28
29
|
class Agent
|
29
30
|
MAXBUF = 20
|
31
|
+
MAXMESSAGEWAIT = MAXBUF * 20
|
30
32
|
|
31
33
|
def initialize(config)
|
32
34
|
Thread::abort_on_exception = true
|
@@ -35,11 +37,13 @@ module MQRPC
|
|
35
37
|
@id = UUID::generate
|
36
38
|
@outbuffer = Hash.new { |h,k| h[k] = [] }
|
37
39
|
@queues = []
|
38
|
-
@receive_queue = Queue.new
|
39
40
|
@topics = []
|
40
|
-
@
|
41
|
-
@
|
42
|
-
|
41
|
+
@receive_queue = Queue.new
|
42
|
+
@want_subscriptions = Queue.new
|
43
|
+
@slidingwindow = Hash.new do |h,k|
|
44
|
+
MQRPC::logger.info "New sliding window for #{k}"
|
45
|
+
h[k] = SizedThreadSafeHash.new(MAXMESSAGEWAIT)
|
46
|
+
end
|
43
47
|
|
44
48
|
@mq = nil
|
45
49
|
@message_operations = Hash.new
|
@@ -49,9 +53,13 @@ module MQRPC
|
|
49
53
|
@amqp_ready = false
|
50
54
|
|
51
55
|
start_amqp
|
56
|
+
|
57
|
+
# Wait for our AMQP thread to get going. Mainly, it needs to set
|
58
|
+
# @mq, so we'll block until it's available.
|
52
59
|
@startup_mutex.synchronize do
|
53
60
|
MQRPC::logger.debug "Waiting for @mq ..."
|
54
61
|
@startup_condvar.wait(@startup_mutex) if !@amqp_ready
|
62
|
+
MQRPC::logger.debug "Got it, continuing with #{self.class} init..."
|
55
63
|
end
|
56
64
|
|
57
65
|
start_receiver
|
@@ -78,10 +86,11 @@ module MQRPC
|
|
78
86
|
MQRPC::logger.info "Subscribing to main queue #{@id}"
|
79
87
|
mq_q = @mq.queue(@id, :auto_delete => true)
|
80
88
|
mq_q.subscribe(:ack =>true) { |hdr, msg| @receive_queue << [hdr, msg] }
|
81
|
-
handle_new_subscriptions
|
89
|
+
#handle_new_subscriptions
|
82
90
|
|
83
91
|
# TODO(sissel): make this a deferred thread that reads from a Queue
|
84
|
-
EM.add_periodic_timer(5) { handle_new_subscriptions }
|
92
|
+
#EM.add_periodic_timer(5) { handle_new_subscriptions }
|
93
|
+
EM.defer { handle_subscriptions }
|
85
94
|
|
86
95
|
EM.add_periodic_timer(1) do
|
87
96
|
# TODO(sissel): add locking
|
@@ -102,11 +111,11 @@ module MQRPC
|
|
102
111
|
end # def start_receiver
|
103
112
|
|
104
113
|
def subscribe(name)
|
105
|
-
@
|
114
|
+
@want_subscriptions << [:queue, name]
|
106
115
|
end # def subscribe
|
107
116
|
|
108
117
|
def subscribe_topic(name)
|
109
|
-
@
|
118
|
+
@want_subscriptions << [:topic, name]
|
110
119
|
end # def subscribe_topic
|
111
120
|
|
112
121
|
def handle_message(hdr, msg_body)
|
@@ -115,12 +124,20 @@ module MQRPC
|
|
115
124
|
obj = [obj]
|
116
125
|
end
|
117
126
|
|
127
|
+
queue = hdr.routing_key
|
118
128
|
obj.each do |item|
|
119
129
|
message = Message.new_from_data(item)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
130
|
+
slidingwindow = @slidingwindow[queue]
|
131
|
+
if message.respond_to?(:from_queue)
|
132
|
+
slidingwindow = @slidingwindow[message.from_queue]
|
133
|
+
end
|
134
|
+
MQRPC::logger.debug "#{Thread.current} Got message #{message.class}##{message.id} on queue #{queue}"
|
135
|
+
MQRPC::logger.debug "Received message: #{message.inspect}"
|
136
|
+
if (message.respond_to?(:in_reply_to) and
|
137
|
+
slidingwindow.include?(message.in_reply_to))
|
138
|
+
MQRPC::logger.debug "Got response to #{message.in_reply_to}"
|
139
|
+
slidingwindow.delete(message.in_reply_to)
|
140
|
+
end
|
124
141
|
name = message.class.name.split(":")[-1]
|
125
142
|
func = "#{name}Handler"
|
126
143
|
|
@@ -129,11 +146,12 @@ module MQRPC
|
|
129
146
|
if (message.respond_to?(:in_reply_to) and
|
130
147
|
@message_operations.has_key?(message.in_reply_to))
|
131
148
|
operation = @message_operations[message.in_reply_to]
|
132
|
-
operation.call
|
149
|
+
operation.call(message)
|
133
150
|
elsif @handler.respond_to?(func)
|
134
151
|
@handler.send(func, message) do |response|
|
135
|
-
|
136
|
-
|
152
|
+
reply_destination = message.reply_to
|
153
|
+
response.from_queue = queue
|
154
|
+
sendmsg(reply_destination, response)
|
137
155
|
end
|
138
156
|
|
139
157
|
# We should allow the message handler to defer acking if they want
|
@@ -150,6 +168,28 @@ module MQRPC
|
|
150
168
|
@amqpthread.join
|
151
169
|
end # run
|
152
170
|
|
171
|
+
def handle_subscriptions
|
172
|
+
while true do
|
173
|
+
type, name = @want_subscriptions.pop
|
174
|
+
case type
|
175
|
+
when :queue
|
176
|
+
next if @queues.include?(name)
|
177
|
+
MQRPC::logger.info "Subscribing to queue #{name}"
|
178
|
+
mq_q = @mq.queue(name, :durable => true)
|
179
|
+
mq_q.subscribe(:ack => true) { |hdr, msg| @receive_queue << [hdr, msg] }
|
180
|
+
@queues << name
|
181
|
+
when :topic
|
182
|
+
MQRPC::logger.info "Subscribing to topic #{name}"
|
183
|
+
exchange = @mq.topic(@config.mqexchange)
|
184
|
+
mq_q = @mq.queue("#{@id}-#{name}",
|
185
|
+
:exclusive => true,
|
186
|
+
:auto_delete => true).bind(exchange, :key => name)
|
187
|
+
mq_q.subscribe { |hdr, msg| @receive_queue << [hdr, msg] }
|
188
|
+
@topics << name
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
153
193
|
def handle_new_subscriptions
|
154
194
|
todo = @want_queues - @queues
|
155
195
|
todo.each do |queue|
|
@@ -162,7 +202,7 @@ module MQRPC
|
|
162
202
|
todo = @want_topics - @topics
|
163
203
|
todo.each do |topic|
|
164
204
|
MQRPC::logger.info "Subscribing to topic #{topic}"
|
165
|
-
exchange = @mq.topic(
|
205
|
+
exchange = @mq.topic(@config.mqexchange)
|
166
206
|
mq_q = @mq.queue("#{@id}-#{topic}",
|
167
207
|
:exclusive => true,
|
168
208
|
:auto_delete => true).bind(exchange, :key => topic)
|
@@ -172,8 +212,6 @@ module MQRPC
|
|
172
212
|
end # handle_new_subscriptions
|
173
213
|
|
174
214
|
def flushout(destination)
|
175
|
-
return unless @mq # wait until we are connected
|
176
|
-
|
177
215
|
msgs = @outbuffer[destination]
|
178
216
|
return if msgs.length == 0
|
179
217
|
data = msgs.to_json
|
@@ -182,28 +220,26 @@ module MQRPC
|
|
182
220
|
end
|
183
221
|
|
184
222
|
def sendmsg_topic(key, msg)
|
185
|
-
return unless @mq # wait until we are connected
|
186
223
|
if (msg.is_a?(RequestMessage) and msg.id == nil)
|
187
224
|
msg.generate_id!
|
188
225
|
end
|
189
226
|
msg.timestamp = Time.now.to_f
|
190
227
|
|
191
228
|
data = msg.to_json
|
192
|
-
@mq.topic(
|
229
|
+
@mq.topic(@config.mqexchange).publish(data, :key => key)
|
193
230
|
end
|
194
231
|
|
195
232
|
def sendmsg(destination, msg, &callback)
|
196
|
-
return unless @mq # wait until we are connected
|
197
233
|
if (msg.is_a?(RequestMessage) and msg.id == nil)
|
198
234
|
msg.generate_id!
|
199
235
|
end
|
200
236
|
msg.timestamp = Time.now.to_f
|
201
237
|
msg.reply_to = @id
|
202
238
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
239
|
+
if msg.is_a?(RequestMessage)
|
240
|
+
MQRPC::logger.debug "Tracking #{msg.class.name}##{msg.id} to #{destination}"
|
241
|
+
@slidingwindow[destination][msg.id] = true
|
242
|
+
end
|
207
243
|
|
208
244
|
if msg.buffer?
|
209
245
|
@outbuffer[destination] << msg
|
@@ -211,6 +247,7 @@ module MQRPC
|
|
211
247
|
flushout(destination)
|
212
248
|
end
|
213
249
|
else
|
250
|
+
MQRPC::logger.debug "#{Thread.current} Sending to #{destination}: #{msg.inspect}"
|
214
251
|
@mq.queue(destination, :durable => true).publish([msg].to_json, :persistent => true)
|
215
252
|
end
|
216
253
|
|
data/lib/mqrpc/config.rb
CHANGED
@@ -5,6 +5,7 @@ module MQRPC
|
|
5
5
|
attr_reader :mquser
|
6
6
|
attr_reader :mqpass
|
7
7
|
attr_reader :mqvhost
|
8
|
+
attr_reader :mqexchange
|
8
9
|
|
9
10
|
def initialize(options = {})
|
10
11
|
@mqhost = options["mqhost"] || "localhost"
|
@@ -12,6 +13,7 @@ module MQRPC
|
|
12
13
|
@mquser = options["mquser"] || "guest"
|
13
14
|
@mqpass = options["mqpass"] || "guest"
|
14
15
|
@mqvhost = options["mqvhost"] || "/"
|
16
|
+
@mqexchange = options["mqexchange"] || "mqrpc.topic"
|
15
17
|
end # def initialize
|
16
18
|
end # class Config
|
17
19
|
end # module MQRPC
|
data/lib/mqrpc/message.rb
CHANGED
@@ -26,6 +26,7 @@ module BindToHash
|
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
29
|
+
private
|
29
30
|
def __genhashpath(key)
|
30
31
|
# TODO(sissel): enforce 'key' needs to be a string or symbol?
|
31
32
|
path = key.split("/").select { |x| x.length > 0 }\
|
@@ -48,14 +49,43 @@ module MQRPC
|
|
48
49
|
header :message_class
|
49
50
|
header :reply_to
|
50
51
|
header :timestamp
|
52
|
+
header :args
|
53
|
+
def self.inherited(subclass)
|
54
|
+
MQRPC::logger.debug "Message '#{subclass.name}' subclasses #{self.name}"
|
55
|
+
@@knowntypes[subclass.name] = subclass
|
56
|
+
|
57
|
+
# Call the class initializer if it has one.
|
58
|
+
if subclass.respond_to?(:class_initialize)
|
59
|
+
subclass.class_initialize
|
60
|
+
end
|
61
|
+
end # def self.inherited
|
62
|
+
|
63
|
+
def self.new_from_data(data)
|
64
|
+
obj = nil
|
65
|
+
name = data["message_class"]
|
66
|
+
if @@knowntypes.has_key?(name)
|
67
|
+
obj = @@knowntypes[name].new
|
68
|
+
else
|
69
|
+
$stderr.puts "No known message class: #{name}, #{data.inspect}"
|
70
|
+
obj = Message.new
|
71
|
+
end
|
72
|
+
obj.data = data
|
73
|
+
return obj
|
74
|
+
end
|
51
75
|
|
52
76
|
def initialize
|
77
|
+
@data = Hash.new
|
78
|
+
want_buffer(false)
|
79
|
+
|
53
80
|
generate_id!
|
81
|
+
self.message_class = self.class.name
|
82
|
+
self.args = Hash.new
|
54
83
|
end
|
55
84
|
|
56
85
|
def generate_id!
|
57
86
|
@@idlock.synchronize do
|
58
87
|
self.id = @@idseq
|
88
|
+
#puts "Generating id. #{self.class}.id == #{self.id}"
|
59
89
|
@@idseq += 1
|
60
90
|
end
|
61
91
|
end
|
@@ -72,35 +102,6 @@ module MQRPC
|
|
72
102
|
@buffer = want_buffer
|
73
103
|
end
|
74
104
|
|
75
|
-
def self.inherited(subclass)
|
76
|
-
MQRPC::logger.debug "Message '#{subclass.name}' subclasses #{self.name}"
|
77
|
-
@@knowntypes[subclass.name] = subclass
|
78
|
-
|
79
|
-
# Call the class initializer if it has one.
|
80
|
-
if subclass.respond_to?(:class_initialize)
|
81
|
-
subclass.class_initialize
|
82
|
-
end
|
83
|
-
end # def self.inherited
|
84
|
-
|
85
|
-
def initialize
|
86
|
-
@data = Hash.new
|
87
|
-
want_buffer(false)
|
88
|
-
self.message_class = self.class.name
|
89
|
-
end
|
90
|
-
|
91
|
-
def self.new_from_data(data)
|
92
|
-
obj = nil
|
93
|
-
name = data["message_class"]
|
94
|
-
if @@knowntypes.has_key?(name)
|
95
|
-
obj = @@knowntypes[name].new
|
96
|
-
else
|
97
|
-
$stderr.puts "No known message class: #{name}, #{data.inspect}"
|
98
|
-
obj = Message.new
|
99
|
-
end
|
100
|
-
obj.data = data
|
101
|
-
return obj
|
102
|
-
end
|
103
|
-
|
104
105
|
def to_json(*args)
|
105
106
|
return @data.to_json(*args)
|
106
107
|
end
|
@@ -110,18 +111,12 @@ module MQRPC
|
|
110
111
|
end # class Message
|
111
112
|
|
112
113
|
class RequestMessage < Message
|
113
|
-
|
114
|
-
|
115
|
-
def initialize
|
116
|
-
super
|
117
|
-
self.args = Hash.new
|
118
|
-
end
|
119
|
-
|
114
|
+
# Nothing.
|
120
115
|
end # class RequestMessage
|
121
116
|
|
122
117
|
class ResponseMessage < Message
|
123
118
|
header :in_reply_to
|
124
|
-
header :
|
119
|
+
header :from_queue
|
125
120
|
|
126
121
|
def initialize(source_request=nil)
|
127
122
|
super()
|
data/lib/mqrpc/messages/ping.rb
CHANGED
data/lib/mqrpc/operation.rb
CHANGED
@@ -16,17 +16,16 @@ module MQRPC
|
|
16
16
|
end # def initialize
|
17
17
|
|
18
18
|
def call(*args)
|
19
|
-
# TODO(sissel):
|
20
|
-
#
|
19
|
+
# TODO(sissel): Come up with a better way for the callback to declare
|
20
|
+
# that it is not done than simply returning ':continue'
|
21
21
|
@mutex.synchronize do
|
22
22
|
ret = @callback.call(*args)
|
23
|
-
if ret
|
24
|
-
MQRPC::logger.debug "operation #{self} finished"
|
23
|
+
if ret != :continue
|
24
|
+
#MQRPC::logger.debug "operation #{self} finished"
|
25
25
|
@finished = true
|
26
26
|
@cv.signal
|
27
|
-
else
|
28
|
-
return ret
|
29
27
|
end
|
28
|
+
return ret
|
30
29
|
end
|
31
30
|
end # def call
|
32
31
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'mqrpc'
|
3
|
+
|
4
|
+
# Thread-safe sized hash similar to SizedQueue.
|
5
|
+
# The only time we will block execution is in setting new items.
|
6
|
+
# That is, SizedThreadSafeHash#[]=
|
7
|
+
class SizedThreadSafeHash
|
8
|
+
def initialize(size)
|
9
|
+
@lock = Mutex.new
|
10
|
+
@size = size
|
11
|
+
@condvar = ConditionVariable.new
|
12
|
+
@data = Hash.new
|
13
|
+
end # def initialize
|
14
|
+
|
15
|
+
# set a key and value
|
16
|
+
def []=(key, value)
|
17
|
+
@lock.synchronize do
|
18
|
+
# If adding a new item, wait if the hash is full
|
19
|
+
if !@data.has_key?(key) and _withlock_full?
|
20
|
+
MQRPC::logger.info "#{self}: Waiting to add key #{key.inspect}, hash is full"
|
21
|
+
#MQRPC::logger.info "#{self}: Keys: #{@data.keys.inspect}"
|
22
|
+
#MQRPC::logger.info "#{self} current thread is #{Thread.current}"
|
23
|
+
#pp @data
|
24
|
+
@condvar.wait(@lock)
|
25
|
+
end
|
26
|
+
@data[key] = value
|
27
|
+
end
|
28
|
+
end # def []=
|
29
|
+
|
30
|
+
# get an value by key
|
31
|
+
def [](key)
|
32
|
+
@lock.synchronize do
|
33
|
+
return @data[key]
|
34
|
+
end
|
35
|
+
end # def []
|
36
|
+
|
37
|
+
# boolean, does the hash have a given key?
|
38
|
+
def has_key?(key)
|
39
|
+
@lock.synchronize do
|
40
|
+
return @data.has_key?(key)
|
41
|
+
end
|
42
|
+
end # def has_key?
|
43
|
+
|
44
|
+
alias :include? :has_key?
|
45
|
+
|
46
|
+
# delete a key
|
47
|
+
def delete(key)
|
48
|
+
@lock.synchronize do
|
49
|
+
was_full = _withlock_full?
|
50
|
+
@data.delete(key)
|
51
|
+
if was_full
|
52
|
+
MQRPC::logger.info "#{self}: signalling non-fullness"
|
53
|
+
@condvar.signal
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end # def delete
|
57
|
+
|
58
|
+
# boolean, indicates true when the hash is full (has size == initialized size)
|
59
|
+
def full?
|
60
|
+
@lock.synchronize do
|
61
|
+
return _withlock_full?
|
62
|
+
end
|
63
|
+
end # def full?
|
64
|
+
|
65
|
+
# return the size (total number of entries) in this hash.
|
66
|
+
def size
|
67
|
+
@lock.synchronize do
|
68
|
+
return @data.size
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return an array of keys for this hash.
|
73
|
+
def keys
|
74
|
+
@lock.synchronize do
|
75
|
+
return @data.keys
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias :length :size
|
79
|
+
|
80
|
+
private
|
81
|
+
def _withlock_full?
|
82
|
+
return @data.size >= @size
|
83
|
+
end
|
84
|
+
end # class SizedThreadSafeHash
|
85
|
+
|
86
|
+
#sh = SizedThreadSafeHash.new(10)
|
87
|
+
#Thread.new do
|
88
|
+
#0.upto(100) do |i|
|
89
|
+
#sh[i] = true
|
90
|
+
#end
|
91
|
+
#end
|
92
|
+
|
93
|
+
#0.upto(100) do |i|
|
94
|
+
#puts sh[i]
|
95
|
+
#sh.delete(i)
|
96
|
+
#sleep 1
|
97
|
+
#end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mqrpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Sissel, Pete Fritchman
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-07 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- lib/mqrpc/message.rb
|
59
59
|
- lib/mqrpc/operation.rb
|
60
60
|
- lib/mqrpc/messages/ping.rb
|
61
|
+
- lib/mqrpc/sizedhash.rb
|
61
62
|
- lib/mqrpc.rb
|
62
63
|
has_rdoc: true
|
63
64
|
homepage: http://code.google.com/p/logstash/wiki/MQRPC
|