emdrb 0.1.1 → 0.1.2

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/README.txt CHANGED
@@ -12,8 +12,9 @@ available in the Ruby standard library.
12
12
 
13
13
  This is a simple but working DRb server implementation that uses
14
14
  EventMachine as its basis, rather than the default implementation that
15
- uses traditional Ruby sockets. This should be somewhat more scalable
16
- than a server using the standard DRb library.
15
+ uses traditional Ruby sockets. This should at the very least play
16
+ better with other programs that have an EventMachine event loop, and
17
+ hopefully provide somewhat better scalability.
17
18
 
18
19
  Obviously, this is a quick and dirty release, just to get something
19
20
  out there, and of course it has a number of limitations.
@@ -47,6 +48,8 @@ making one with the standard library DRb:
47
48
  EMDRb.start_service(URI, TimeServer.new)
48
49
  EMDRb.thread.join
49
50
 
51
+ It is also possible to create
52
+
50
53
  == REQUIREMENTS:
51
54
 
52
55
  * Obviously, EMDRb requires EventMachine.
data/lib/emdrb/emdrb.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Homepage:: http://emdrb.rubyforge.org/
5
5
  # License:: GNU General Public License / Ruby License
6
6
  #
7
- # $Id: emdrb.rb 17 2008-12-02 03:58:23Z dido $
7
+ # $Id: emdrb.rb 28 2009-01-22 05:13:01Z dido $
8
8
  #
9
9
  #----------------------------------------------------------------------------
10
10
  #
@@ -27,10 +27,78 @@ module EMDRb
27
27
  DEFAULT_LOAD_LIMIT = 256 * 102400
28
28
  DEFAULT_SAFE_LEVEL = 0
29
29
 
30
+ ##
31
+ # Common protocol elements for distributed Ruby, used by both the
32
+ # client and server.
33
+ #
34
+ module DRbProtocolCommon
35
+ ##
36
+ # This method will dump an object +obj+ using Ruby's marshalling
37
+ # capabilities. It will make a proxy to the object instead if
38
+ # the object is undumpable. The dumps are basically data produced
39
+ # by Marshal::dump prefixed by a 32-bit length field in network
40
+ # byte order.
41
+ #
42
+ def dump(obj, error=false)
43
+ if obj.kind_of? DRb::DRbUndumped
44
+ obj = make_proxy(obj, error)
45
+ end
46
+ begin
47
+ str = Marshal::dump(obj)
48
+ rescue
49
+ str = Marshal::dump(make_proxy(obj, error))
50
+ end
51
+ return([str.size].pack("N") + str)
52
+ end
53
+
54
+ ##
55
+ # Create a proxy for +obj+ that is declared to be undumpable.
56
+ #
57
+ def make_proxy(obj, error=false)
58
+ return(error ? DRb::DRbRemoteError.new(obj) : DRb::DRbObject.new(obj))
59
+ end
60
+
61
+ ##
62
+ # Receive data from the caller. This basically receives packets
63
+ # containing objects marshalled using Ruby's Marshal::dump prefixed
64
+ # by a length. These objects are unmarshalled and processed by the
65
+ # internal object request state machine (DRbServerProtocol#receive_obj
66
+ # below). If an error of any kind occurs herein, the exception is
67
+ # propagated to the caller.
68
+ def receive_data(data)
69
+ @msgbuffer << data
70
+ while @msgbuffer.length > 4
71
+ length = @msgbuffer.unpack("N")[0]
72
+ if length > @load_limit
73
+ raise DRb::DRbConnError, "too large packet #{length}"
74
+ end
75
+
76
+ if @msgbuffer.length < length - 4
77
+ # not enough data for this length, return to event loop
78
+ # to wait for more.
79
+ break
80
+ end
81
+ length, message, @msgbuffer = @msgbuffer.unpack("Na#{length}a*")
82
+ receive_obj(obj_load(message))
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Load a serialized object.
88
+ def obj_load(message)
89
+ begin
90
+ return(Marshal::load(message))
91
+ rescue NameError, ArgumentError
92
+ return(DRb::DRbUnknown.new($!, message))
93
+ end
94
+ end
95
+ end
96
+
30
97
  ##
31
98
  # EventMachine server module for DRb.
32
99
  #
33
100
  module DRbServerProtocol
101
+ include DRbProtocolCommon
34
102
  ##
35
103
  # The front object for this server connection.
36
104
  attr_accessor :front
@@ -80,69 +148,15 @@ module EMDRb
80
148
  @server = @argv = @argc = nil
81
149
  end
82
150
 
83
- ##
84
- # Receive data from the caller. This basically receives packets
85
- # containing objects marshalled using Ruby's Marshal::dump prefixed
86
- # by a length. These objects are unmarshalled and processed by the
87
- # internal object request state machine. If an error of any kind
88
- # occurs herein, the exception is propagated to the caller.
89
- def receive_data(data)
90
- begin
91
- @msgbuffer << data
92
- while @msgbuffer.length > 4
93
- length = @msgbuffer.unpack("N")[0]
94
- if length > @load_limit
95
- raise DRb::DRbConnError, "too large packet #{length}"
96
- end
97
-
98
- if @msgbuffer.length < length - 4
99
- # not enough data for this length, return to event loop
100
- # to wait for more.
101
- break
102
- end
103
- length, message, @msgbuffer = @msgbuffer.unpack("Na#{length}a*")
104
- add_obj(obj_load(message))
105
- end
106
- rescue Exception => e
107
- send_reply(false, e)
108
- end
109
- end
110
-
111
151
  private
112
152
 
113
- ##
114
- # This method will dump an object +obj+ using Ruby's marshalling
115
- # capabilities. It will make a proxy to the object instead if
116
- # the object is undumpable. The dumps are basically data produced
117
- # by Marshal::dump prefixed by a 32-bit length field in network
118
- # byte order.
119
- #
120
- def dump(obj, error=false)
121
- if obj.kind_of? DRb::DRbUndumped
122
- obj = make_proxy(obj, error)
123
- end
124
- begin
125
- str = Marshal::dump(obj)
126
- rescue
127
- str = Marshal::dump(make_proxy(obj, error))
128
- end
129
- return([str.size].pack("N") + str)
130
- end
131
-
132
- ##
133
- # Create a proxy for +obj+ that is declared to be undumpable.
134
- #
135
- def make_proxy(obj, error=false)
136
- return(error ? Drb::DRbRemoteError.new(obj) : DRb::DRbObject.new(obj))
137
- end
138
-
139
153
  ##
140
154
  # Send a reply to the caller. The return value for distributed Ruby
141
155
  # over the wire is the success as a boolean true or false value, followed
142
156
  # by a dump of the data.
143
157
  #
144
158
  def send_reply(succ, result)
145
- send_data(dump(succ) + dump(result, !succ))
159
+ send_data(dump(succ) + dump(result, !succ))
146
160
  end
147
161
 
148
162
  ##
@@ -229,7 +243,22 @@ module EMDRb
229
243
  return(@idconv.to_obj(ref))
230
244
  end
231
245
 
232
- def add_obj(obj)
246
+ ##
247
+ # This is the main state machine that processes distributed Ruby calls.
248
+ # A DRb client basically sends several pieces of data in sequence, each
249
+ # of which corresponds to a state of this machine.
250
+ #
251
+ # 1. :ref - this gives a reference to a DRb server running on the
252
+ # caller, mainly used to provide a mechanism for accessing undumpable
253
+ # objects on the caller.
254
+ # 2. :msg - a symbol giving the method to be called on this server.
255
+ # 3. :argc - an integer count of the number of arguments on the caller.
256
+ # 4. :argv - repeats an :argc number of times, the actual arguments
257
+ # sent by the caller.
258
+ # 5. :block - the block passed by the caller (generally a DRbObject
259
+ # wrapping a Proc object).
260
+ #
261
+ def receive_obj(obj)
233
262
  @request[@state] = obj
234
263
  case @state
235
264
  when :ref
@@ -252,7 +281,7 @@ module EMDRb
252
281
  @state = :block
253
282
  end
254
283
  when :block
255
- @request[:argv] = @argv
284
+ @request[:argv] = @argv
256
285
  @state = :ref
257
286
  send_reply(*perform)
258
287
  @request = {}
@@ -262,18 +291,13 @@ module EMDRb
262
291
  end
263
292
  end
264
293
 
265
- ##
266
- # Load a serialized object.
267
- def obj_load(message)
268
- begin
269
- return(Marshal::load(message))
270
- rescue NameError, ArgumentError
271
- return(DRb::DRbUnknown.new($!, message))
272
- end
273
- end
274
-
275
294
  end
276
295
 
296
+ ##
297
+ # Class representing a drb server instance. This subclasses DRb::DRbServer
298
+ # for brevity. DRbServer instances are normally created indirectly using
299
+ # either EMDRb.start service (which emulates DRb.start_service) or via
300
+ # EMDRb.start_drbserver (designed to be called from within an event loop).
277
301
  class DRbServer < DRb::DRbServer
278
302
  def initialize(uri=nil, front=nil, config_or_acl=nil)
279
303
  if Hash === config_or_acl
@@ -290,8 +314,6 @@ module EMDRb
290
314
  @front = front
291
315
  @idconv = @config[:idconv]
292
316
  @safe_level = @config[:safe_level]
293
- @thread = run
294
- EMDRb.regist_server(self)
295
317
  end
296
318
 
297
319
  private
@@ -319,23 +341,25 @@ module EMDRb
319
341
  end
320
342
  end
321
343
 
322
- def run
323
- Thread.start do
324
- host, port, opt = EMDRb::parse_uri(@uri)
325
- if host.size == 0
326
- host = self.class.host_inaddr_any
327
- end
328
- EventMachine::run do
329
- EventMachine::start_server(host, port, DRbServerProtocol) do |conn|
330
- Thread.current['DRb'] = { 'client' => conn, 'server' => self }
331
- conn.front = @front
332
- conn.load_limit = @config[:load_limit]
333
- conn.argc_limit = @config[:argc_limit]
334
- conn.idconv = @config[:idconv]
335
- conn.server = self
336
- conn.safe_level = self.safe_level
337
- end
338
- end
344
+ public
345
+
346
+ ##
347
+ # Start a DRb server from within an event loop.
348
+ #
349
+ def start_drb_server
350
+ @thread = Thread.current
351
+ host, port, opt = EMDRb::parse_uri(@uri)
352
+ if host.size == 0
353
+ host = self.class.host_inaddr_any
354
+ end
355
+ EventMachine::start_server(host, port, DRbServerProtocol) do |conn|
356
+ Thread.current['DRb'] = { 'client' => conn, 'server' => self }
357
+ conn.front = @front
358
+ conn.load_limit = @config[:load_limit]
359
+ conn.argc_limit = @config[:argc_limit]
360
+ conn.idconv = @config[:idconv]
361
+ conn.server = self
362
+ conn.safe_level = self.safe_level
339
363
  end
340
364
  end
341
365
 
@@ -357,9 +381,38 @@ module EMDRb
357
381
  module_function :parse_uri
358
382
 
359
383
  @primary_server = nil
384
+ @eventloop = nil
360
385
 
386
+ ##
387
+ # This is the 'bare bones' start_service which can be used to
388
+ # start a DRb service from within an existing event loop.
389
+ def start_drbserver(uri=nil, front=nil, config=nil)
390
+ serv = DRbServer.new(uri, front, config)
391
+ serv.start_drb_server
392
+ return(serv)
393
+ end
394
+ module_function :start_drbserver
395
+
396
+ ##
397
+ # This start_service emulates DRb#start_service.
398
+ #
361
399
  def start_service(uri=nil, front=nil, config=nil)
362
- @primary_server = DRbServer.new(uri, front, config)
400
+ unless EventMachine::reactor_running?
401
+ @eventloop = Thread.new do
402
+ EventMachine::run do
403
+ # Start an empty event loop. The DRb server(s) will be started
404
+ # by EM#next_tick calls.
405
+ end
406
+ end
407
+ end
408
+ queue = Queue.new
409
+ EventMachine::next_tick do
410
+ queue << self.start_drbserver(uri, front, config)
411
+ end
412
+ serv = queue.shift
413
+ @primary_server = serv
414
+ EMDRb.regist_server(serv)
415
+ return(serv)
363
416
  end
364
417
  module_function :start_service
365
418
 
@@ -403,4 +456,93 @@ module EMDRb
403
456
  end
404
457
  module_function :thread
405
458
 
459
+
460
+ ##
461
+ # Client protocol module
462
+ module DRbClientProtocol
463
+ include DRbProtocolCommon
464
+
465
+ attr_accessor :ref
466
+ attr_accessor :msg_id
467
+ attr_accessor :args
468
+ attr_accessor :block
469
+ attr_accessor :df
470
+
471
+ def post_init
472
+ @msgbuffer = ""
473
+ @idconv = DRb::DRbIdConv.new
474
+ @load_limit = DEFAULT_LOAD_LIMIT
475
+ end
476
+
477
+ def connection_completed
478
+ @connected = true
479
+ send_request(@ref, @msg_id, @args, @block)
480
+ @state = :succ
481
+ @succ = nil
482
+ @result = nil
483
+ end
484
+
485
+ def send_request(ref, msgid, arg, block)
486
+ ary = []
487
+ ary.push(dump(ref.__drbref))
488
+ ary.push(dump(msg_id.id2name))
489
+ ary.push(dump(arg.length))
490
+ arg.each do |e|
491
+ ary.push(dump(e))
492
+ end
493
+ ary.push(dump(block))
494
+ send_data(ary.join(''))
495
+ end
496
+
497
+ def receive_obj(obj)
498
+ if @state == :succ
499
+ @succ = obj
500
+ @state = :result
501
+ else
502
+ @result = obj
503
+ @state = :succ
504
+ @df.set_deferred_status(:succeeded, [@succ, @result])
505
+ # close the connection after the call succeeds.
506
+ close_connection
507
+ end
508
+ end
509
+ end
510
+
511
+ ##
512
+ # Object wrapping a reference to a remote drb object.
513
+ #
514
+ # Method calls on this object are relayed to the remote object
515
+ # that this object is a stub for.
516
+ class DRbObject < DRb::DRbObject
517
+ def initialize(obj, uri=nil)
518
+ @uri = nil
519
+ @ref = nil
520
+ if obj.nil?
521
+ return if uri.nil?
522
+ @uri = uri
523
+ ref = nil
524
+ @host, @port, @opt = EMDRb::parse_uri(@uri)
525
+ else
526
+ @ref = obj
527
+ end
528
+ end
529
+
530
+ ##
531
+ # Perform an asynchronous call to the remote object. This can only
532
+ # be used from within an event loop. It returns a deferrable to which
533
+ # callbacks can be attached.
534
+ def send_async(msg_id, *a, &b)
535
+ df = EventMachine::DefaultDeferrable.new
536
+ EventMachine.connect(@host, @port, DRbClientProtocol) do |c|
537
+ c.ref = self
538
+ c.msg_id = msg_id
539
+ c.args = a
540
+ c.block = b
541
+ c.df = df
542
+ end
543
+ return(df)
544
+ end
545
+
546
+ end
547
+
406
548
  end
data/lib/emdrb/version.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Homepage:: http://emdrb.rubyforge.org/
5
5
  # License:: GNU Lesser General Public License / Ruby License
6
6
  #
7
- # $Id: version.rb 20 2008-12-02 04:00:25Z dido $
7
+ # $Id: version.rb 26 2009-01-22 04:48:43Z dido $
8
8
  #
9
9
  #----------------------------------------------------------------------------
10
10
  #
@@ -26,7 +26,7 @@ module EMDRb
26
26
 
27
27
  MAJOR = 0
28
28
  MINOR = 1
29
- TINY = 1
29
+ TINY = 2
30
30
 
31
31
  # The version of EMDRb in use.
32
32
  STRING = [ MAJOR, MINOR, TINY ].join(".")
data/test/test_emdrb.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Homepage:: http://emdrb.rubyforge.org/
5
5
  # License:: GNU General Public License / Ruby License
6
6
  #
7
- # $Id: test_emdrb.rb 19 2008-12-02 03:59:14Z dido $
7
+ # $Id: test_emdrb.rb 27 2009-01-22 05:11:56Z dido $
8
8
  #
9
9
  #----------------------------------------------------------------------------
10
10
  #
@@ -46,8 +46,15 @@ class TestServer
46
46
  end
47
47
 
48
48
  class EMDRbTest < Test::Unit::TestCase
49
- def test_drb
49
+ def setup
50
50
  EMDRb.start_service("druby://:12345", TestServer.new)
51
+ end
52
+
53
+ def teardown
54
+ EMDRb.thread.kill
55
+ end
56
+
57
+ def test_server
51
58
  o = DRbObject.new_with_uri("druby://localhost:12345")
52
59
  DRb.start_service
53
60
  assert_equal(1, o.identity(1))
@@ -61,4 +68,29 @@ class EMDRbTest < Test::Unit::TestCase
61
68
  assert_equal(5040, val)
62
69
  end
63
70
 
71
+ def test_client
72
+ o = EMDRb::DRbObject.new(nil, "druby://localhost:12345")
73
+ q = Queue.new
74
+ EventMachine::next_tick do
75
+ o.send_async(:identity, 1).callback do |data|
76
+ assert(data[0])
77
+ assert_equal(1, data[1])
78
+ q << data
79
+ end
80
+ end
81
+ q.shift
82
+
83
+ # EventMachine::next_tick do
84
+ # val = 1
85
+ # df = o.send_async(:blockyield, 1,2,3,4,5,6,7) { |x| val *= x; val }
86
+ # df.callback do |data|
87
+ # assert(data[0])
88
+ # assert_equal(5040, data[1])
89
+ # q << data
90
+ # end
91
+ # end
92
+ # q.shift
93
+
94
+ end
95
+
64
96
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emdrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - dido@imperium.ph
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-02 00:00:00 +08:00
12
+ date: 2009-01-22 00:00:00 +08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15