simrpc 0.2 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ require 'spec/rake/spectask'
8
8
  require 'rake/gempackagetask'
9
9
 
10
10
  GEM_NAME="simrpc"
11
- PKG_VERSION=0.2
11
+ PKG_VERSION=0.4
12
12
 
13
13
  desc "Run all specs"
14
14
  Spec::Rake::SpecTask.new('spec') do |t|
@@ -30,11 +30,14 @@ SPEC = Gem::Specification.new do |s|
30
30
 
31
31
  s.required_ruby_version = '>= 1.8.1'
32
32
  s.required_rubygems_version = Gem::Requirement.new(">= 1.3.3")
33
- # FIXME require qpid
33
+ s.add_development_dependency('rspec', '~> 1.3.0')
34
+ s.add_dependency('uuid', '~> 2.3.1')
35
+ s.add_dependency('activesupport', '~> 2.3.8')
36
+ # !!! we also need the ruby qpid wrapper. which is not currently packaged in gem form
34
37
 
35
38
  s.author = "Mohammed Morsi"
36
39
  s.email = "movitto@yahoo.com"
37
- s.date = %q{2010-03-11}
40
+ s.date = %q{2010-09-04}
38
41
  s.description = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
39
42
  s.summary = %q{simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.}
40
43
  s.homepage = %q{http://projects.morsi.org/Simrpc}
data/lib/simrpc.rb CHANGED
@@ -30,9 +30,10 @@ lib = File.dirname(__FILE__)
30
30
 
31
31
  require lib + '/simrpc/common'
32
32
  require lib + '/simrpc/exceptions'
33
+ require lib + '/simrpc/thread_pool'
33
34
  require lib + '/simrpc/schema'
34
35
  require lib + '/simrpc/message'
35
36
  require lib + '/simrpc/qpid_adapter'
36
37
  require lib + '/simrpc/node'
37
38
 
38
- require 'activesupport' # for inflector
39
+ require 'active_support' # for inflector
data/lib/simrpc/common.rb CHANGED
@@ -24,6 +24,7 @@
24
24
  # OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
26
  require 'logger'
27
+ require 'uuid'
27
28
 
28
29
  module Simrpc
29
30
 
@@ -43,4 +44,12 @@ class Logger
43
44
  end
44
45
  end
45
46
 
47
+ # ID bank, generates unique ids
48
+ class IDBank
49
+ # FIXME right now generates uuid and grabs first 8 chars for message ids, not enough for tru randomness
50
+ def self.generate
51
+ UUID.new.generate[0...8]
52
+ end
46
53
  end
54
+
55
+ end # module Simrpc
@@ -33,15 +33,15 @@ module Message
33
33
  # Simrpc::Message formatter helper module
34
34
  class Formatter
35
35
 
36
- # helper method to format a data field,
37
- # prepending a fixed size to it
36
+ # Helper method to format a data field,
37
+ # prepending a size to it
38
38
  def self.format_with_size(data)
39
39
  # currently size is set to a 8 digit int
40
40
  len = "%08d" % data.to_s.size
41
41
  len + data.to_s
42
42
  end
43
43
 
44
- # helper method to parse a data field
44
+ # Helper method to parse a data field
45
45
  # off the front of a data sequence, using the
46
46
  # formatted size. Returns parsed data field
47
47
  # and remaining data sequence. If optional
@@ -56,6 +56,24 @@ class Formatter
56
56
  return parsed, remaining if data_class.nil?
57
57
  return data_class.from_s(parsed), remaining
58
58
  end
59
+
60
+ # Helper to format a data field of a fixed size,
61
+ def self.format_with_fixed_size(size, data)
62
+ # we will only accept the first <size> characters of data
63
+ # FIXME throw exception if data.size < size
64
+ data.to_s[0..size-1]
65
+ end
66
+
67
+ # Helper to parse data of a fixed size and return it and the remaining data.
68
+ # If optional class is given, the from_s method will be invoked w/ the parsed
69
+ # data field and returned w/ the remaining data sequence instead
70
+ def self.parse_from_formatted_with_fixed_size(size, data, data_class = nil)
71
+ # FIXME throw exception if data.size < size
72
+ parsed = data[0..size-1]
73
+ remaining = data[size...data.size]
74
+ return parsed, remaining if data_class.nil?
75
+ return data_class.from_s(parsed), remaining
76
+ end
59
77
  end
60
78
 
61
79
  # a single field trasnmitted via a message,
@@ -83,19 +101,21 @@ end
83
101
  # header contains various descriptive properies
84
102
  # about a message
85
103
  class Header
86
- attr_accessor :type, :target
104
+ attr_accessor :id, :type, :target
87
105
 
88
106
  def initialize(args = {})
107
+ @id = args[:id].nil? ? "" : args[:id]
89
108
  @type = args[:type].nil? ? "" : args[:type]
90
109
  @target = args[:target].nil? ? "" : args[:target]
91
110
  end
92
111
 
93
112
  def to_s
94
- Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
113
+ Formatter::format_with_fixed_size(8, @id) + Formatter::format_with_size(@type) + Formatter::format_with_size(@target)
95
114
  end
96
115
 
97
116
  def self.from_s(data)
98
117
  header = Header.new
118
+ header.id, data = Formatter::parse_from_formatted_with_fixed_size(8, data)
99
119
  header.type, data = Formatter::parse_from_formatted(data)
100
120
  header.target, data = Formatter::parse_from_formatted(data)
101
121
  return header
@@ -106,7 +126,7 @@ end
106
126
  class Body
107
127
  attr_accessor :fields
108
128
 
109
- def initialize
129
+ def initialize(args = {})
110
130
  @fields = []
111
131
  end
112
132
 
@@ -134,9 +154,9 @@ end
134
154
  class Message
135
155
  attr_accessor :header, :body
136
156
 
137
- def initialize
138
- @header = Header.new
139
- @body = Body.new
157
+ def initialize(args = {})
158
+ @header = Header.new(args.has_key?(:header) ? args[:header] : {})
159
+ @body = Body.new(args.has_key?(:body) ? args[:body] : {})
140
160
  end
141
161
 
142
162
  def to_s
data/lib/simrpc/node.rb CHANGED
@@ -33,89 +33,71 @@ class MethodMessageController
33
33
  @schema_def = schema_def
34
34
  end
35
35
 
36
- # generate new new method message, setting the message
36
+ # Generate new new method message, setting the message
37
37
  # target to the specified method name, and setting the fields
38
38
  # on the message to the method arguments
39
39
  def generate(method_name, args)
40
- @schema_def.methods.each { |method|
41
- if method.name == method_name
42
- msg = Message::Message.new
43
- msg.header.type = 'request'
44
- msg.header.target = method.name
45
-
46
- # loop through each param, convering corresponding
47
- # argument to message field and adding it to msg
48
- i = 0
49
- method.parameters.each { |param|
50
- field = Message::Field.new
51
- field.name = param.name
52
- field.value = param.to_s(args[i], @schema_def)
53
- msg.body.fields.push field
54
- i += 1
55
- }
56
-
57
- return msg
58
- end
59
- }
60
- return nil
40
+ mmethod = @schema_def.methods.find { |method| method.name == method_name }
41
+ return nil if mmethod.nil?
42
+
43
+ msg = Message::Message.new :header => {:id => IDBank.generate, :type => 'request', :target => mmethod.name }
44
+
45
+ # if we have too few arguments, fill rest in w/ default values
46
+ if mmethod.parameters.size > args.size
47
+ mmethod.parameters[args.size...mmethod.parameters.size].each { |param| args << param.default }
48
+ end
49
+
50
+ msg.body.fields = (0...mmethod.parameters.size).collect { |i|
51
+ Message::Field.new(:name => mmethod.parameters[i].name,
52
+ :value => mmethod.parameters[i].to_s(args[i], @schema_def))
53
+ }
54
+
55
+ return msg
61
56
  end
62
57
 
63
58
  # should be invoked when a message is received,
64
59
  # takes a message, converts it into a method call, and calls the corresponding
65
60
  # handler in the provided schema. Takes return arguments and sends back to caller
66
61
  def message_received(node, message, reply_to)
67
- message = Message::Message::from_s(message)
68
- @schema_def.methods.each { |method|
69
-
70
- if method.name == message.header.target
71
- Logger.info "received method #{method.name} message "
72
-
73
- # for request messages, dispatch to method handler
74
- if message.header.type != 'response' && method.handler != nil
75
- # order the params
76
- params = []
77
- method.parameters.each { |data_field|
78
- value_field = message.body.fields.find { |f| f.name == data_field.name }
79
- params.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
80
- }
81
-
82
- Logger.info "invoking #{method.name} handler "
83
-
84
- # invoke method handler
85
- return_values = method.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
86
- # FIXME throw a catch block around this call to catch all handler exceptions
87
- return_values = [return_values] unless return_values.is_a? Array
88
-
89
- # if method returns no values, do not return response
90
- unless method.return_values.size == 0
91
-
92
- # consruct and send response message using return values
93
- response = Message::Message.new
94
- response.header.type = 'response'
95
- response.header.target = method.name
96
- (0...method.return_values.size).each { |rvi|
97
- field = Message::Field.new
98
- field.name = method.return_values[rvi].name
99
- field_def = method.return_values.find { |rv| rv.name == field.name }
100
- field.value = field_def.to_s(return_values[rvi], @schema_def) unless field_def.nil? # TODO what if field_def is nil
101
- response.body.fields.push field
102
- }
103
- Logger.info "responding to #{reply_to}"
104
- node.send_message(reply_to, response)
105
-
106
- end
107
-
108
- # for response values just return converted return values
109
- else
110
- results = []
111
- method.return_values.each { |data_field|
112
- value_field = message.body.fields.find { |f| f.name == data_field.name }
113
- results.push data_field.from_s(value_field.value, @schema_def) unless value_field.nil? # TODO what if value_field is nil
114
- }
115
- return results
116
- end
117
- end
118
- }
62
+ message = Message::Message::from_s(message)
63
+ mmethod = @schema_def.methods.find { |method| method.name == message.header.target }
64
+ return nil if mmethod.nil?
65
+
66
+ Logger.info "received method #{mmethod.name} message "
67
+
68
+ # For request messages, dispatch to method handler
69
+ if message.header.type == 'request'
70
+ return nil if mmethod.handler.nil?
71
+
72
+ # collect the method params
73
+ params = (0...mmethod.parameters.size).collect { |i| mmethod.parameters[i].from_s(message.body.fields[i].value, @schema_def) }
74
+
75
+ Logger.info "invoking #{mmethod.name} handler "
76
+
77
+ # invoke method handler
78
+ return_values = mmethod.handler.call(*params) # FIXME handlers can't use 'return' as this will fall through here
79
+ # FIXME throw a catch block around this call to catch all handler exceptions
80
+ return_values = [return_values] unless return_values.is_a? Array
81
+
82
+ # consruct and send response message using return values
83
+ response = Message::Message.new :header => {:id => message.header.id, :type => 'response', :target => mmethod.name }
84
+ response.body.fields = (0...mmethod.return_values.size).collect { |i|
85
+ field_def = mmethod.return_values[i]
86
+ Message::Field.new :name => field_def.name, :value => field_def.to_s(return_values[i], @schema_def)
87
+ # TODO if mmethod.return_values.size > return_values.size, fill in w/ default values
88
+ }
89
+ Logger.info "responding to #{reply_to}"
90
+ node.send_message(reply_to, response)
91
+ return message.header.id, params
92
+
93
+ # For response values just return converted return values
94
+ else
95
+ results = (0...mmethod.return_values.size).collect { |i|
96
+ mmethod.return_values[i].from_s(message.body.fields[i].value, @schema_def)
97
+ }
98
+ return message.header.id, results
99
+
100
+ end
119
101
  end
120
102
  end
121
103
 
@@ -140,15 +122,17 @@ class Node
140
122
  end
141
123
  raise ArgumentError, "schema_def cannot be nil" if @schema_def.nil?
142
124
  @mmc = MethodMessageController.new(@schema_def)
143
- @message_lock = Semaphore.new(1)
144
- @message_lock.wait
125
+
126
+ # hash of message id's -> response locks
127
+ @message_locks = {}
145
128
 
146
129
  # FIXME currently not allowing for any other params to be passed into
147
130
  # QpidAdapter::Node such as broker ip or port, NEED TO FIX THIS
131
+ # NOTE see QpidAdapter::Node for limitation on using 'inspect'
148
132
  @qpid_node = QpidAdapter::Node.new(:id => @id)
149
133
  @qpid_node.async_accept { |node, msg, reply_to|
150
- results = @mmc.message_received(node, msg, reply_to)
151
- message_received(results)
134
+ mid, results = @mmc.message_received(node, msg, reply_to)
135
+ message_received(mid, results)
152
136
  }
153
137
  end
154
138
 
@@ -158,9 +142,14 @@ class Node
158
142
  end
159
143
 
160
144
  # implements, message_received callback to be notified when qpid receives a message
161
- def message_received(results)
162
- @message_results = results
163
- @message_lock.signal
145
+ def message_received(mid, results)
146
+ @message_results = results unless results.nil?
147
+ @message_locks[mid].signal unless mid.nil? || !@message_locks.has_key?(mid)
148
+ end
149
+
150
+ # Terminate node operations
151
+ def terminate
152
+ @qpid_node.terminate
164
153
  end
165
154
 
166
155
  # wait until the node is no longer accepting messages
@@ -188,12 +177,16 @@ class Node
188
177
 
189
178
  # block if we are expecting return values
190
179
  if @schema_def.methods.find{|m| m.name == method_name}.return_values.size != 0
191
- @message_lock.wait # block until response received
180
+ @message_locks[msg.header.id] = Semaphore.new(1)
181
+ @message_locks[msg.header.id].wait
182
+ @message_locks[msg.header.id].wait # block until response received
183
+ @message_locks.delete(msg.header.id)
192
184
 
193
185
  # return return values
194
186
  #@message_received.body.fields.collect { |f| f.value }
195
187
  return *@message_results
196
188
  end
189
+ return nil
197
190
  end
198
191
 
199
192
  # can invoke schema methods directly on Node instances, this will catch
@@ -37,7 +37,8 @@ module QpidAdapter
37
37
  # network which has its own exchange and queue which it listens on
38
38
  class Node
39
39
  private
40
- # helper method to generate a random id
40
+ # Helper method to generate a random id
41
+ # TODO use uuid since we're pulling in that gem anyways for message ids
41
42
  def gen_uuid
42
43
  ["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") %
43
44
  Array.new(16) {|x| rand(0xff) }
@@ -77,6 +78,9 @@ class Node
77
78
  end
78
79
 
79
80
  ### create underlying tcp connection
81
+ # NOTE pretty big bug in ruby/qpid preventing us from using 'inspect'
82
+ # on this class (causes segfault)
83
+ # https://issues.apache.org/jira/browse/QPID-2405
80
84
  @conn = Qpid::Connection.new(TCPSocket.new(broker,port))
81
85
  @conn.start
82
86
 
@@ -85,6 +89,10 @@ class Node
85
89
 
86
90
  @children = {}
87
91
 
92
+ # threads pool to handle incoming requests
93
+ # FIXME make the # of threads and timeout configurable)
94
+ @thread_pool = ThreadPool.new(10, :timeout => 5)
95
+
88
96
  @accept_lock = Semaphore.new(1)
89
97
 
90
98
  # qpid constructs that will be created for node
@@ -126,28 +134,34 @@ class Node
126
134
 
127
135
  # start receiving messages
128
136
  @incoming.listen{ |msg|
129
- Logger.info "queue #{@queue} received message #{msg.body.to_s.size} #{msg.body}"
137
+ Logger.info "queue #{@queue} received message #{msg.body}"
130
138
  reply_to = msg.get(:message_properties).reply_to.routing_key
131
- handler.call(self, msg.body, reply_to)
139
+ # FIXME should delete handler threads as they complete
140
+ # FIXME handler timeout
141
+ job = ThreadPoolJob.new { handler.call(self, msg.body, reply_to) }
142
+ @thread_pool << job
132
143
  }
133
144
  end
134
145
 
135
146
  # block until accept operation is complete
136
147
  def join
137
148
  @accept_lock.wait
149
+ #FIXME @thread_pool.join
138
150
  end
139
151
 
140
152
  # instructs QpidServer to stop accepting, blocking
141
153
  # untill all accepting operations have terminated
142
154
  def terminate
143
155
  Logger.warn "terminating qpid session"
156
+ @thread_pool.stop
144
157
  unless @incoming.nil?
145
158
  @incoming.stop
146
159
  @incoming.close
147
160
  @accept_lock.signal
148
161
  end
162
+ @ssn.queue_delete(@queue)
163
+ @ssn.exchange_delete(@exchange)
149
164
  @ssn.close
150
- # TODO undefine the @queue/@exchange
151
165
  end
152
166
 
153
167
  # send a message to the specified routing_key
data/lib/simrpc/schema.rb CHANGED
@@ -66,9 +66,12 @@ module_function :primitive_from_str
66
66
  class DataFieldDef
67
67
  attr_accessor :type, :name, :associated
68
68
 
69
- # indicates this data field should be ignored if it has a null value
69
+ # Indicates this data field should be ignored if it has a null value
70
70
  attr_accessor :ignore_null
71
71
 
72
+ # Indicates the default value of the field
73
+ attr_accessor :default
74
+
72
75
  def initialize(args = {})
73
76
  @type = args[:type] unless args[:type].nil?
74
77
  @name = args[:name] unless args[:name].nil?
@@ -138,7 +141,7 @@ class DataFieldDef
138
141
  # Provide schema_def for :obj or :array data fields associated w/ non-primitive types
139
142
  # # coverted_classes is a recursive helper array used/maintained internally
140
143
  def from_s(str, schema_def = nil, converted_classes = [])
141
- if str == ""
144
+ if str == "" # || str == "nil" # FIXME uncomment & test
142
145
  return nil
143
146
 
144
147
  elsif Schema::is_primitive?(@type)
@@ -390,8 +393,10 @@ class Parser
390
393
  data_field = DataFieldDef.new
391
394
  data_field.type = element.attributes["type"].intern
392
395
  data_field.name = element.attributes["name"]
396
+ data_field.default = data_field.from_s(element.attributes["default"]) unless element.attributes["default"].nil?
393
397
  data_field.associated = element.attributes["associated"].intern unless element.attributes["associated"].nil?
394
398
  data_field.ignore_null = true if element.attributes.include? "ignore_null"
399
+ data_field
395
400
  return data_field
396
401
  end
397
402
  end
@@ -0,0 +1,137 @@
1
+ # Thread Pool
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person
6
+ # obtaining a copy of this software and associated documentation
7
+ # files (the "Software"), to deal in the Software without
8
+ # restriction, including without limitation the rights to use,
9
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ # OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ require 'thread'
27
+
28
+ module Simrpc
29
+
30
+ # Work item to be executed in thread pool
31
+ class ThreadPoolJob
32
+ attr_accessor :handler
33
+ attr_accessor :params
34
+
35
+ def initialize(*params, &block)
36
+ @params = params
37
+ @handler = block
38
+ end
39
+ end
40
+
41
+ # ActiveObject pattern, encapsulate each thread pool thread in object
42
+ class ThreadPoolJobRunner
43
+ attr_accessor :time_started
44
+
45
+ def initialize(thread_pool)
46
+ @thread_pool = thread_pool
47
+ end
48
+
49
+ def run
50
+ @thread = Thread.new {
51
+ until @thread_pool.terminate
52
+ work = @thread_pool.next_job
53
+ @time_started = Time.now
54
+ work.handler.call *work.params unless work.nil?
55
+ @time_started = nil
56
+ end
57
+ }
58
+ end
59
+
60
+ def stop
61
+ @thread.kill
62
+ @thread.join
63
+ end
64
+
65
+ def join
66
+ @thread.join
67
+ end
68
+ end
69
+
70
+ # Launches a specified number of threads on instantiation,
71
+ # assigning work to them as it arrives
72
+ class ThreadPool
73
+ attr_accessor :terminate
74
+
75
+ # Create a thread pool with a specified number of threads
76
+ def initialize(num_threads, args = {})
77
+ @num_threads = num_threads
78
+ @timeout = args[:timeout]
79
+ @job_runners = []
80
+ @work_queue = []
81
+ @work_queue_lock = Mutex.new
82
+ @work_queue_cv = ConditionVariable.new
83
+
84
+ @terminate = false
85
+
86
+ 0.upto(@num_threads) { |i|
87
+ runner = ThreadPoolJobRunner.new(self)
88
+ @job_runners << runner
89
+ runner.run
90
+ }
91
+
92
+ # optional timeout thread
93
+ unless @timeout.nil?
94
+ @timeout_thread = Thread.new {
95
+ until @terminate
96
+ sleep @timeout
97
+ @job_runners.each { |jr|
98
+ if !jr.time_started.nil? && (Time.now - jr.time_started > @timeout)
99
+ jr.stop
100
+ jr.run
101
+ end
102
+ }
103
+ end
104
+ }
105
+ end
106
+ end
107
+
108
+ # Add work to the pool
109
+ def <<(work)
110
+ @work_queue_lock.synchronize {
111
+ @work_queue << work
112
+ @work_queue_cv.signal
113
+ }
114
+ end
115
+
116
+ # Return the next job queued up, blocking until one is received
117
+ # if none are present
118
+ def next_job
119
+ work = nil
120
+ @work_queue_lock.synchronize {
121
+ # wait until we have work
122
+ @work_queue_cv.wait(@work_queue_lock) if @work_queue.empty?
123
+ work = @work_queue.shift
124
+ }
125
+ work
126
+ end
127
+
128
+ # Terminate the thread pool
129
+ def stop
130
+ @terminate = true
131
+ @work_queue_lock.synchronize {
132
+ @work_queue.clear
133
+ }
134
+ @job_runners.each { |jr| jr.stop }
135
+ end
136
+ end
137
+ end
data/spec/common_spec.rb CHANGED
@@ -24,3 +24,13 @@
24
24
  # OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
26
  require File.dirname(__FILE__) + '/spec_helper'
27
+
28
+ describe "Simrpc" do
29
+
30
+ it "should generate unique ids" do
31
+ id1 = IDBank.generate
32
+ id2 = IDBank.generate
33
+ id1.should_not == id2
34
+ end
35
+
36
+ end
data/spec/message_spec.rb CHANGED
@@ -35,6 +35,12 @@ describe "Simrpc::Message" do
35
35
  assert_equal ["foobar", ""], Message::Formatter::parse_from_formatted(formatted)
36
36
  end
37
37
 
38
+ it "should format and parsed a data field with a fixed size" do
39
+ formatted = Message::Formatter::format_with_fixed_size(3, 123456)
40
+ formatted.should == "123"
41
+ assert_equal ["123", ""], Message::Formatter::parse_from_formatted_with_fixed_size(3, formatted)
42
+ end
43
+
38
44
  it "should convert a field to and from a string" do
39
45
  field = Message::Field.new(:name => "foo", :value => "bar")
40
46
  field_s = field.to_s
@@ -72,13 +78,14 @@ describe "Simrpc::Message" do
72
78
 
73
79
  it "should convert a message to and from a string" do
74
80
  msg = Message::Message.new
81
+ msg.header.id = "12345678"
75
82
  msg.header.type = 'request'
76
83
  msg.header.target = 'method'
77
84
  msg.body.fields.push Message::Field.new(:name => "foo", :value => "bar")
78
85
  msg.body.fields.push Message::Field.new(:name => "money", :value => "lotsof")
79
86
  msg_s = msg.to_s
80
87
 
81
- msg_s.should == "0000002900000007request00000006method000000650000002200000003foo00000003bar0000002700000005money00000006lotsof"
88
+ msg_s.should == "000000371234567800000007request00000006method000000650000002200000003foo00000003bar0000002700000005money00000006lotsof"
82
89
 
83
90
  msg_o = Message::Message.from_s(msg_s)
84
91
  msg_o.header.type.should == 'request'
data/spec/node_spec.rb CHANGED
@@ -70,9 +70,43 @@ describe "Simrpc::Node" do
70
70
  client.send_message("server-queue", msg)
71
71
  finished_lock.wait()
72
72
 
73
+ server.terminate
74
+ client.terminate
75
+
73
76
  # FIXME test method that doesn't have any return values
74
77
  end
75
78
 
79
+ it "should handle default values" do
80
+ schema_def = Schema::Parser.parse(:schema => TEST_SCHEMA)
81
+ mmc = MethodMessageController.new(schema_def)
82
+ msg = mmc.generate('foo_method', [2])
83
+
84
+ finished_lock = Semaphore.new(1)
85
+ finished_lock.wait()
86
+
87
+ schema_def.methods[0].handler = lambda { |some_int, floating_point_number|
88
+ some_int.to_i.should == 2
89
+ floating_point_number.to_f.should == 5.6 # test default value
90
+ return "yo", MyClass.new
91
+ }
92
+
93
+ server = QpidAdapter::Node.new :id => "server"
94
+ server.async_accept { |node, msg, reply_to|
95
+ mmc.message_received(node, msg, reply_to)
96
+ }
97
+
98
+ client = QpidAdapter::Node.new :id => 'client'
99
+ client.async_accept { |node, msg, reply_to|
100
+ #msg = Message::Message.from_s(msg)
101
+ finished_lock.signal()
102
+ }
103
+ client.send_message("server-queue", msg)
104
+ finished_lock.wait()
105
+
106
+ server.terminate
107
+ client.terminate
108
+ end
109
+
76
110
  it "it should run properly" do
77
111
  server = Node.new(:id => "server3", :schema => TEST_SCHEMA)
78
112
  client = Node.new(:id => "client3", :schema => TEST_SCHEMA, :destination => "server3")
@@ -94,6 +128,9 @@ describe "Simrpc::Node" do
94
128
  my_class_instance.float_member.should == 4.2
95
129
 
96
130
  # FIXME test method that doesn't have any return values
131
+
132
+ client.terminate
133
+ server.terminate
97
134
  end
98
135
 
99
136
  end
data/spec/qpid_spec.rb CHANGED
@@ -38,6 +38,7 @@ describe "Simrpc::QpidAdapter" do
38
38
  ssn.error?.should == false
39
39
  #ssn.closed.should == false
40
40
  id.should == "test1"
41
+ qpid.terminate
41
42
  end
42
43
 
43
44
  # ensure we define a queue and exchange
@@ -67,6 +68,21 @@ describe "Simrpc::QpidAdapter" do
67
68
  #assert !binding_result.queue_not_found?
68
69
  #assert !binding_result.queue_not_matched?
69
70
  #assert !binding_result.key_not_found?
71
+
72
+ node.terminate
73
+ end
74
+
75
+ it "should terminate queue and exchange" do
76
+ node = QpidAdapter::Node.new :id => "qpid_test3"
77
+ node.terminate
78
+
79
+ # need to start a new session since terminate closes the previous one
80
+ conn = Qpid::Connection.new(TCPSocket.new('localhost', 5672))
81
+ conn.start
82
+ ssn = conn.session(UUID.new.generate)
83
+
84
+ assert ssn.queue_query("qpid_test3-queue").queue.nil?
85
+ assert ssn.exchange_query("qpid_test3-exchange").not_found
70
86
  end
71
87
 
72
88
  # test sending/receiving a message
@@ -87,6 +103,9 @@ describe "Simrpc::QpidAdapter" do
87
103
  }
88
104
  client.send_message("server1-queue", "test-data")
89
105
  finished_lock.wait()
106
+
107
+ server.terminate
108
+ client.terminate
90
109
  end
91
110
 
92
111
  end
data/spec/schema_spec.rb CHANGED
@@ -49,6 +49,7 @@ describe "Simrpc::Schema" do
49
49
  schema_def.methods[0].parameters[0].name.should == 'some_int'
50
50
  schema_def.methods[0].parameters[1].type.should == :float
51
51
  schema_def.methods[0].parameters[1].name.should == 'floating_point_number'
52
+ schema_def.methods[0].parameters[1].default.should == 5.6
52
53
  schema_def.methods[0].return_values.size.should == 2
53
54
  schema_def.methods[0].return_values[0].type.should == :str
54
55
  schema_def.methods[0].return_values[0].name.should == 'a_string'
data/spec/spec_helper.rb CHANGED
@@ -40,7 +40,7 @@ TEST_SCHEMA =
40
40
  "<schema>"+
41
41
  " <method name='foo_method'>" +
42
42
  " <param type='int' name='some_int'/>"+
43
- " <param type='float' name='floating_point_number'/>"+
43
+ " <param type='float' name='floating_point_number' default='5.6' />"+
44
44
  " <return_value type='str' name='a_string' />" +
45
45
  " <return_value type='obj' name='my_class_instance' associated='MyClass' />" +
46
46
  " </method>"+
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simrpc
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.2"
4
+ hash: 3
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 4
9
+ version: "0.4"
5
10
  platform: ruby
6
11
  authors:
7
12
  - Mohammed Morsi
@@ -9,10 +14,57 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-03-11 00:00:00 -05:00
17
+ date: 2010-09-04 00:00:00 -04:00
13
18
  default_executable:
14
- dependencies: []
15
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 27
29
+ segments:
30
+ - 1
31
+ - 3
32
+ - 0
33
+ version: 1.3.0
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: uuid
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 1
45
+ segments:
46
+ - 2
47
+ - 3
48
+ - 1
49
+ version: 2.3.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: activesupport
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 19
61
+ segments:
62
+ - 2
63
+ - 3
64
+ - 8
65
+ version: 2.3.8
66
+ type: :runtime
67
+ version_requirements: *id003
16
68
  description: simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.
17
69
  email: movitto@yahoo.com
18
70
  executables: []
@@ -25,6 +77,7 @@ files:
25
77
  - lib/simrpc/message.rb
26
78
  - lib/simrpc/common.rb
27
79
  - lib/simrpc/semaphore.rb
80
+ - lib/simrpc/thread_pool.rb
28
81
  - lib/simrpc/qpid_adapter.rb
29
82
  - lib/simrpc/exceptions.rb
30
83
  - lib/simrpc/schema.rb
@@ -49,21 +102,31 @@ rdoc_options: []
49
102
  require_paths:
50
103
  - lib
51
104
  required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
52
106
  requirements:
53
107
  - - ">="
54
108
  - !ruby/object:Gem::Version
109
+ hash: 53
110
+ segments:
111
+ - 1
112
+ - 8
113
+ - 1
55
114
  version: 1.8.1
56
- version:
57
115
  required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
58
117
  requirements:
59
118
  - - ">="
60
119
  - !ruby/object:Gem::Version
120
+ hash: 29
121
+ segments:
122
+ - 1
123
+ - 3
124
+ - 3
61
125
  version: 1.3.3
62
- version:
63
126
  requirements: []
64
127
 
65
128
  rubyforge_project:
66
- rubygems_version: 1.3.5
129
+ rubygems_version: 1.3.7
67
130
  signing_key:
68
131
  specification_version: 3
69
132
  summary: simrpc is a simple Ruby module for rpc communication, using Apache QPID as the transport mechanism.