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 +5 -2
- data/lib/emdrb/emdrb.rb +230 -88
- data/lib/emdrb/version.rb +2 -2
- data/test/test_emdrb.rb +34 -2
- metadata +2 -2
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
|
16
|
-
|
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
|
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
|
-
|
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
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
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
|
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 =
|
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
|
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
|
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.
|
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:
|
12
|
+
date: 2009-01-22 00:00:00 +08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|