mqrpc 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
- @want_queues = []
41
- @want_topics = []
42
- #@slidingwindow = LogStash::SlidingWindowSet.new
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
- @want_queues << name
114
+ @want_subscriptions << [:queue, name]
106
115
  end # def subscribe
107
116
 
108
117
  def subscribe_topic(name)
109
- @want_topics << name
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
- #if @slidingwindow.include?(message.id)
121
- #puts "Removing ack for #{message.id}"
122
- #@slidingwindow.delete(message.id)
123
- #end
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 message
149
+ operation.call(message)
133
150
  elsif @handler.respond_to?(func)
134
151
  @handler.send(func, message) do |response|
135
- reply = message.reply_to
136
- sendmsg(reply, response)
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("amq.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("amq.topic").publish(data, :key => key)
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
- #if (msg.is_a?(RequestMessage) and !msg.is_a?(ResponseMessage))
204
- #MQRPC::logger.info "Tracking #{msg.class.name}##{msg.id}"
205
- #@slidingwindow << msg.id
206
- #end
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
- header :args
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 :args
119
+ header :from_queue
125
120
 
126
121
  def initialize(source_request=nil)
127
122
  super()
@@ -1,5 +1,5 @@
1
1
  require 'rubygems'
2
- require 'lib/mqrpc'
2
+ require 'mqrpc'
3
3
 
4
4
  module MQRPC::Messages
5
5
  class PingRequest < MQRPC::RequestMessage
@@ -16,17 +16,16 @@ module MQRPC
16
16
  end # def initialize
17
17
 
18
18
  def call(*args)
19
- # TODO(sissel): allow the callback to simply invoke 'finished' on this
20
- # operation rather than requiring it to emit ':finished'
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 == :finished
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.1
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-05 00:00:00 -08:00
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