mqrpc 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -78,11 +78,7 @@ module MQRPC
78
78
  case state
79
79
  when :blocked
80
80
  MQRPC::logger.info("Queue '#{k}' is full, unsubscribing from #{source}")
81
- exchange = @mq.topic(@config.mqexchange, :durable => true)
82
- mq_q = @mq.queue(source, :durable => true)
83
- mq_q.bind(exchange, :key => "*")
84
- mq_q.unsubscribe
85
- @queues.delete(source)
81
+ unsubscribe(source)
86
82
  when :ready
87
83
  MQRPC::logger.info("Queue '#{k}' is ready, resubscribing to #{source}")
88
84
  subscribe(source)
@@ -113,6 +109,7 @@ module MQRPC
113
109
 
114
110
  def start_amqp
115
111
  @amqpthread = Thread.new do
112
+ Thread.current[:name] = "AMQP"
116
113
  # Create connection to AMQP, and in turn, the main EventMachine loop.
117
114
  amqp_config = {:host => @config.mqhost,
118
115
  :port => @config.mqport,
@@ -133,11 +130,9 @@ module MQRPC
133
130
  subscribe(@id)
134
131
 
135
132
  # TODO(sissel): make this a deferred thread that reads from a Queue
136
- #EM.add_periodic_timer(5) { handle_new_subscriptions }
137
133
  EM.defer { handle_subscriptions }
138
134
 
139
135
  EM.add_periodic_timer(1) do
140
- # TODO(sissel): add locking
141
136
  @outbuffer.each_key { |dest| flushout(dest) }
142
137
  @outbuffer.clear
143
138
  end
@@ -147,6 +142,7 @@ module MQRPC
147
142
 
148
143
  def start_receiver
149
144
  Thread.new do
145
+ Thread.current[:name] = "receiver"
150
146
  while true
151
147
  header, message = @receive_queue.pop
152
148
  handle_message(header, message)
@@ -155,9 +151,27 @@ module MQRPC
155
151
  end # def start_receiver
156
152
 
157
153
  def subscribe(name)
154
+ MQRPC::logger.info "Wanting to subscribe to queue #{name}"
158
155
  @want_subscriptions << [:queue, name]
159
156
  end # def subscribe
160
157
 
158
+ def unsubscribe(name)
159
+ exchange = @mq.topic(@config.mqexchange, :durable => true)
160
+ mq_q = @mq.queue(name, :durable => true)
161
+ mq_q.bind(exchange, :key => "*")
162
+
163
+ op = Operation.new
164
+ mq_q.unsubscribe { op.finished }
165
+ op.wait_until_finished
166
+ @queues.delete(name)
167
+
168
+ #mq_q.unsubscribe { @queues.delete(name) }
169
+ ## wait for unsubscribe to finish; it's async
170
+ #while @queues.member?(name)
171
+ #sleep(0.1)
172
+ #end
173
+ end # def unsubscribe
174
+
161
175
  def subscribe_topic(name)
162
176
  @want_subscriptions << [:topic, name]
163
177
  end # def subscribe_topic
@@ -177,8 +191,13 @@ module MQRPC
177
191
  return
178
192
  end
179
193
 
180
- # TODO(sissel): handle any exceptions we might get from here.
181
- obj = JSON::load(msg_body)
194
+ begin
195
+ obj = JSON::load(msg_body)
196
+ rescue JSON::ParserError
197
+ MQRPC::logger.warn("Skipping non-JSON message: #{msg_body}")
198
+ hdr.ack
199
+ return
200
+ end
182
201
  if !obj.is_a?(Array)
183
202
  obj = [obj]
184
203
  end
@@ -222,24 +241,52 @@ module MQRPC
222
241
  end # def handle_message
223
242
 
224
243
  def run
244
+ Thread.current[:name] ||= "#{self.class.name}#run"
225
245
  @amqpthread.join
226
246
  end # run
227
247
 
228
248
  def can_receive?(message_class)
249
+ if self.class.message_handlers == nil
250
+ self.class.message_handlers = []
251
+ end
252
+
229
253
  return self.class.message_handlers.include?(message_class)
230
254
  end
231
255
 
232
256
  def handle_subscriptions
257
+ Thread.current[:name] = "subscriptionhandler"
233
258
  while true do
234
- type, name = @want_subscriptions.pop
235
- case type
259
+ queuetype, name = @want_subscriptions.pop
260
+
261
+ case queuetype
236
262
  when :queue
237
- next if @queues.include?(name)
263
+ if @queues.include?(name)
264
+ MQRPC::logger.info "Ignoring subscription request to queue "\
265
+ "#{name}, already subscribed."
266
+ next
267
+ end
238
268
  MQRPC::logger.info "Subscribing to queue #{name}"
269
+ # Send a dummy message to queue #{name} so there's at least
270
+ # one message to receive, and wake up our Operation.
271
+ sendmsg(name, DummyMessage.new)
239
272
  exchange = @mq.topic(@config.mqexchange, :durable => true)
240
273
  mq_q = @mq.queue(name, :durable => true)
241
274
  mq_q.bind(exchange, :key => "*")
242
- mq_q.subscribe(:ack => true) { |hdr, msg| @receive_queue << [hdr, msg] }
275
+ op = Operation.new
276
+ mq_q.subscribe(:ack => true) do |hdr, msg|
277
+ op.finished
278
+ return if msg.is_a?(DummyMessage)
279
+ queue = hdr.routing_key
280
+ MQRPC::logger.info("received message on #{queue}")
281
+ @receive_queue << [hdr, msg]
282
+ MQRPC::logger.info("finished receiving message on #{queue}")
283
+ MQRPC::logger.info("msg: #{msg}")
284
+ MQRPC::logger.info("#{queue} queue size: #{@receive_queue.length}")
285
+ end
286
+ # Wait until we receive our first message (might be DummyMessage,
287
+ # doesn't matter) -- this confirms we are subscribed. subscribe
288
+ # is async...
289
+ op.wait_until_finished
243
290
  @queues << name
244
291
  when :topic
245
292
  MQRPC::logger.info "Subscribing to topic #{name}"
@@ -249,30 +296,9 @@ module MQRPC
249
296
  :auto_delete => true).bind(exchange, :key => name)
250
297
  mq_q.subscribe { |hdr, msg| @receive_queue << [hdr, msg] }
251
298
  @topics << name
252
- end
253
- end
254
- end
255
-
256
- def handle_new_subscriptions
257
- todo = @want_queues - @queues
258
- todo.each do |queue|
259
- MQRPC::logger.info "Subscribing to queue #{queue}"
260
- mq_q = @mq.queue(queue, :durable => true)
261
- mq_q.subscribe(:ack => true) { |hdr, msg| @receive_queue << [hdr, msg] }
262
- @queues << queue
263
- end # todo.each
264
-
265
- todo = @want_topics - @topics
266
- todo.each do |topic|
267
- MQRPC::logger.info "Subscribing to topic #{topic}"
268
- exchange = @mq.topic(@config.mqexchange)
269
- mq_q = @mq.queue("#{@id}-#{topic}",
270
- :exclusive => true,
271
- :auto_delete => true).bind(exchange, :key => topic)
272
- mq_q.subscribe { |hdr, msg| @receive_queue << [hdr, msg] }
273
- @topics << topic
274
- end # todo.each
275
- end # handle_new_subscriptions
299
+ end # case queuetype
300
+ end # while true
301
+ end # def handle_subscriptions
276
302
 
277
303
  def flushout(destination)
278
304
  msgs = @outbuffer[destination]
@@ -315,7 +341,8 @@ module MQRPC
315
341
  end
316
342
 
317
343
  if block_given?
318
- op = Operation.new(callback)
344
+ op = Operation.new &callback
345
+ MQRPC::logger.debug "New operation for #{msg.id}"
319
346
  @message_operations[msg.id] = op
320
347
  return op
321
348
  end
@@ -1,7 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'mqrpc/messages/ping'
3
3
 
4
- module MQRPC; module Functions; module Ping
4
+ module MQRPC; module Functions; class Ping
5
+ #handle MQRPC::Messages::PingRequest, :PingRequestHandler
6
+
5
7
  def PingRequestHandler(request)
6
8
  MQRPC::logger.debug "received PingRequest (#{request.pingdata})"
7
9
  response = MQRPC::Messages::PingResponse.new
@@ -13,3 +13,16 @@ module MQRPC
13
13
  @logger = logger
14
14
  end
15
15
  end
16
+
17
+ # Make logger include the thread id.
18
+ class FormatterWithThread < Logger::Formatter
19
+ Format = "%s, [%s#%d/%s] %5s -- %s: %s\n"
20
+
21
+ def call(severity, time, progname, msg)
22
+ thread_id = (Thread.current[:name] or Thread.current)
23
+ Format % [severity[0..0], format_datetime(time), $$, thread_id,
24
+ severity, progname, msg2str(msg)]
25
+ end
26
+ end
27
+
28
+ MQRPC::logger.formatter = FormatterWithThread.new
@@ -108,6 +108,10 @@ module MQRPC
108
108
  # Nothing.
109
109
  end # class RequestMessage
110
110
 
111
+ class DummyMessage < Message
112
+ # Nothing.
113
+ end # class DummyMessage
114
+
111
115
  class ResponseMessage < Message
112
116
  header :in_reply_to
113
117
  header :from_queue
@@ -0,0 +1,132 @@
1
+ require 'json'
2
+ require 'thread'
3
+ require 'mqrpc/logger'
4
+
5
+ module BindToHash
6
+ def header(method, key=nil)
7
+ key = method.to_s if key == nil
8
+ hashbind(method, "/#{key}")
9
+ end
10
+
11
+ def argument(method, key=nil)
12
+ key = method.to_s if key == nil
13
+ hashbind(method, "/args/#{key}")
14
+ end
15
+
16
+ private
17
+ def hashbind(method, key)
18
+ hashpath = __genhashpath(key)
19
+ self.class_eval %(
20
+ def #{method}
21
+ return #{hashpath}
22
+ end
23
+ def #{method}=(val)
24
+ #{hashpath} = val
25
+ end
26
+ )
27
+ end
28
+
29
+ private
30
+ def __genhashpath(key)
31
+ # TODO(sissel): enforce 'key' needs to be a string or symbol?
32
+ path = key.split("/").select { |x| x.length > 0 }\
33
+ .map { |x| "[#{x.inspect}]" }
34
+ return "@data#{path.join("")}"
35
+ end
36
+ end # modules BindToHash
37
+
38
+ module MQRPC
39
+ class Message
40
+ extend BindToHash
41
+ @@idseq = 0
42
+ @@idlock = Mutex.new
43
+ @@knowntypes = Hash.new
44
+ attr_accessor :data
45
+
46
+ # Message attributes
47
+ header :id
48
+ header :message_class
49
+ header :delayable
50
+ header :reply_to
51
+ header :timestamp
52
+ header :args
53
+
54
+ def self.inherited(subclass)
55
+ MQRPC::logger.debug "Message '#{subclass.name}' subclasses #{self.name}"
56
+ @@knowntypes[subclass.name] = subclass
57
+
58
+ # Call the class initializer if it has one.
59
+ if subclass.respond_to?(:class_initialize)
60
+ subclass.class_initialize
61
+ end
62
+ end # def self.inherited
63
+
64
+ def self.new_from_data(data)
65
+ obj = nil
66
+ name = data["message_class"]
67
+ if @@knowntypes.has_key?(name)
68
+ obj = @@knowntypes[name].new
69
+ else
70
+ $stderr.puts "No known message class: #{name}, #{data.inspect}"
71
+ obj = Message.new
72
+ end
73
+ obj.data = data
74
+ return obj
75
+ end
76
+
77
+ def initialize
78
+ @data = Hash.new
79
+ # Don't delay messages by defualt
80
+ self.delayable = false
81
+
82
+ generate_id!
83
+ self.message_class = self.class.name
84
+ self.args = Hash.new
85
+ end
86
+
87
+ def generate_id!
88
+ @@idlock.synchronize do
89
+ self.id = @@idseq
90
+ #puts "Generating id. #{self.class}.id == #{self.id}"
91
+ @@idseq += 1
92
+ end
93
+ end
94
+
95
+ def age
96
+ return Time.now.to_f - timestamp
97
+ end
98
+
99
+ def to_json(*args)
100
+ return @data.to_json(*args)
101
+ end
102
+
103
+ protected
104
+ attr :data
105
+ end # class Message
106
+
107
+ class RequestMessage < Message
108
+ # Nothing.
109
+ end # class RequestMessage
110
+
111
+ class ResponseMessage < Message
112
+ header :in_reply_to
113
+ header :from_queue
114
+
115
+ def initialize(source_request=nil)
116
+ super()
117
+
118
+ # Copy the request id if we are given a source_request
119
+ if source_request.is_a?(RequestMessage)
120
+ self.in_reply_to = source_request.id
121
+ #self.delayable = source_request.delayable
122
+ end
123
+ self.args = Hash.new
124
+ end
125
+
126
+ # Report the success of the request this response is for.
127
+ # Should be implemented by subclasses.
128
+ def success?
129
+ raise NotImplementedError
130
+ end
131
+ end # class ResponseMessage
132
+ end # module MQRPC
@@ -0,0 +1,19 @@
1
+ ***************
2
+ *** 108,113 ****
3
+ # Nothing.
4
+ end # class RequestMessage
5
+
6
+ class ResponseMessage < Message
7
+ header :in_reply_to
8
+ header :from_queue
9
+ --- 108,117 ----
10
+ # Nothing.
11
+ end # class RequestMessage
12
+
13
+ + class DummyMessage < Message
14
+ + # Nothing.
15
+ + end # class DummyMessage
16
+ +
17
+ class ResponseMessage < Message
18
+ header :in_reply_to
19
+ header :from_queue
@@ -6,9 +6,14 @@ module MQRPC
6
6
  # A single message operation
7
7
  # * Takes a callback to call when a message is received
8
8
  # * Allows you to wait for the operation to complete.
9
- # * An operation is 'complete' when the callback returns :finished
9
+ # * An operation is 'complete' when the callback returns.
10
+ #
11
+ # If your callback returns :continue, then we will not call finished.
12
+ # This allows you to have an operation that is invoked multiple times,
13
+ # such as for streaming blocks of data, and only finish when you know
14
+ # you are done.
10
15
  class Operation
11
- def initialize(callback)
16
+ def initialize(&callback)
12
17
  @mutex = Mutex.new
13
18
  @callback = callback
14
19
  @cv = ConditionVariable.new
@@ -21,9 +26,7 @@ module MQRPC
21
26
  @mutex.synchronize do
22
27
  ret = @callback.call(*args)
23
28
  if ret != :continue
24
- #MQRPC::logger.debug "operation #{self} finished"
25
- @finished = true
26
- @cv.signal
29
+ _withlock_finished
27
30
  end
28
31
  return ret
29
32
  end
@@ -34,15 +37,35 @@ module MQRPC
34
37
  # immediately.
35
38
  def wait_until_finished
36
39
  @mutex.synchronize do
37
- if !finished?
40
+ if !_withlock_finished?
38
41
  @cv.wait(@mutex)
39
42
  end
40
43
  end
41
44
  end # def wait_until_finished
42
45
 
43
- protected
46
+ # Is the operation finished yet?
44
47
  def finished?
48
+ @mutex.synchronize do
49
+ return _withlock_finished?
50
+ end
51
+ end # def finished?
52
+
53
+ # Declare that the operation is finished.
54
+ def finished
55
+ @mutex.synchronize do
56
+ return _withlock_finished
57
+ end
58
+ end # def finished
59
+
60
+ protected
61
+ def _withlock_finished?
45
62
  return @finished
46
63
  end # def finished?
64
+
65
+ def _withlock_finished
66
+ @finished = true
67
+ @cv.signal
68
+ end
69
+
47
70
  end # class Operation
48
71
  end # module MQRPC
@@ -1,11 +1,12 @@
1
1
  require 'thread'
2
2
  require 'mqrpc'
3
3
 
4
+ # A mutex which logs every enter/exit on synchronize
4
5
  class TrackingMutex < Mutex
5
6
  def synchronize(&blk)
6
- #puts "Enter synchronize #{self} @ #{Thread.current} + #{caller[0]}"
7
+ MQRPC::logger.debug "Enter synchronize #{self} @ #{caller[0]}"
7
8
  super { blk.call }
8
- #puts "Exit synchronize #{self} @ #{Thread.current} + #{caller[0]}"
9
+ MQRPC::logger.debug "Exit synchronize #{self} @ #{caller[0]}"
9
10
  end # def synchronize
10
11
  end # clas TrackingMutex < Mutex
11
12
 
@@ -17,7 +18,7 @@ class SizedThreadSafeHash
17
18
  attr_reader :size
18
19
 
19
20
  def initialize(size, &callback)
20
- @lock = TrackingMutex.new
21
+ @lock = Mutex.new
21
22
  @size = size
22
23
  @condvar = ConditionVariable.new
23
24
  @data = Hash.new
@@ -30,7 +31,7 @@ class SizedThreadSafeHash
30
31
  @lock.synchronize do
31
32
  # If adding a new item, wait if the hash is full
32
33
  if !@data.has_key?(key) and _withlock_full?
33
- MQRPC::logger.info "#{self}: Waiting to add key #{key.inspect}, hash is full (thraed #{Thread.current})"
34
+ MQRPC::logger.info "#{self}: Waiting to add key #{key.inspect}, hash is full (thread #{Thread.current})"
34
35
  if @state != :blocked
35
36
  @state = :blocked
36
37
  @callback.call(@state) if @callback
@@ -0,0 +1,32 @@
1
+ Index: lib/mqrpc/agent.rb
2
+ ===================================================================
3
+ --- agent.rb (revision 318)
4
+ +++ agent.rb (working copy)
5
+ @@ -267,10 +267,16 @@
6
+ end
7
+ MQRPC::logger.info "Subscribing to queue #{name}"
8
+ @queues << name
9
+ + # Send a dummy message to queue #{name} so there's at least
10
+ + # one message to receive, and wake up our Operation.
11
+ + sendmsg(name, DummyMessage.new)
12
+ exchange = @mq.topic(@config.mqexchange, :durable => true)
13
+ mq_q = @mq.queue(name, :durable => true)
14
+ mq_q.bind(exchange, :key => "*")
15
+ + op = Operation.new
16
+ mq_q.subscribe(:ack => true) do |hdr, msg|
17
+ + op.finished
18
+ + return if msg.is_a?(DummyMessage)
19
+ queue = hdr.routing_key
20
+ MQRPC::logger.info("received message on #{queue}")
21
+ @receive_queue << [hdr, msg]
22
+ @@ -278,6 +284,10 @@
23
+ MQRPC::logger.info("msg: #{msg}")
24
+ MQRPC::logger.info("#{queue} queue size: #{@receive_queue.length}")
25
+ end
26
+ + # Wait until we receive our first message (might be DummyMessage,
27
+ + # doesn't matter) -- this confirms we are subscribed. subscribe
28
+ + # is async...
29
+ + op.wait_until_finished
30
+ when :topic
31
+ MQRPC::logger.info "Subscribing to topic #{name}"
32
+ exchange = @mq.topic(@config.mqexchange, :durable => true)
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.6
4
+ version: 0.0.7
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-14 00:00:00 -08:00
12
+ date: 2009-11-20 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -53,12 +53,15 @@ extra_rdoc_files: []
53
53
  files:
54
54
  - lib/mqrpc/agent.rb
55
55
  - lib/mqrpc/config.rb
56
+ - lib/mqrpc/test.diff
56
57
  - lib/mqrpc/logger.rb
57
58
  - lib/mqrpc/functions/ping.rb
58
59
  - lib/mqrpc/message.rb
60
+ - lib/mqrpc/message.rb.orig
59
61
  - lib/mqrpc/operation.rb
60
62
  - lib/mqrpc/messages/ping.rb
61
63
  - lib/mqrpc/sizedhash.rb
64
+ - lib/mqrpc/message.rb.rej
62
65
  - lib/mqrpc.rb
63
66
  has_rdoc: true
64
67
  homepage: http://code.google.com/p/logstash/wiki/MQRPC