From 20e165a04090a024db4d295d00af6ed1ac67578d Mon Sep 17 00:00:00 2001 From: wew Date: Fri, 12 Sep 2003 02:29:38 +0000 Subject: Added documentation in RDoc format. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4548 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/drb/drb.rb | 837 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 799 insertions(+), 38 deletions(-) diff --git a/lib/drb/drb.rb b/lib/drb/drb.rb index e6ea35cfc1..ed0ceb542f 100644 --- a/lib/drb/drb.rb +++ b/lib/drb/drb.rb @@ -1,54 +1,455 @@ -=begin -= distributed Ruby --- dRuby 2.0.4 - Copyright (c) 1999-2003 Masatoshi SEKI - You can redistribute it and/or modify it under the same terms as Ruby. -=end +# +# drb/drb.rb -- distributed Ruby --- dRuby 2.0.4 +# +#-- +# Copyright (c) 1999-2003 Masatoshi SEKI You can redistribute it and/or +# modify it under the same terms as Ruby. +#++ +# +# Author: Masatoshi SEKI +# +# Documentation: William Webber (william@williamwebber.com) +# +# == Overview +# +# dRuby is a distributed object system for Ruby. It allows an object in one +# Ruby process to invoke methods on an object in another Ruby process on the +# same or a different machine. +# +# The Ruby standard library contains the core classes of the dRuby package. +# However, the full package also includes access control lists and the +# Rinda tuple-space distributed task management system, as well as a +# large number of samples. The full dRuby package can be downloaded from +# the dRuby home page (see *References*). +# +# For an introduction and examples of usage see the documentation to the +# DRb module. +# +# == References +# +# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.html] +# The dRuby home page, in Japanese. Contains the full dRuby package +# and links to other Japanese-language sources. +# +# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.en.html] +# The English version of the dRuby home page. +# +# [http://www.chadfowler.com/ruby/drb.html] +# A quick tutorial introduction to using dRuby by Chad Fowler. +# +# [http://www.linux-mag.com/2002-09/ruby_05.html] +# A tutorial introduction to dRuby in Linux Magazine by Dave Thomas. +# Includes a discussion of Rinda. +# +# [http://www.eng.cse.dmu.ac.uk/~hgs/ruby/dRuby/] +# Links to English-language Ruby material collected by Hugh Sasse. +# +# [http://www.rubycentral.com/book/ospace.html] +# The chapter from *Programming* *Ruby* by Dave Thomas and Andy Hunt +# which discusses dRuby. +# +# [http://www.clio.ne.jp/home/web-i31s/Flotuard/Ruby/PRC2K_seki/dRuby.en.html] +# Translation of presentation on Ruby by Masatoshi Seki. require 'socket' require 'thread' require 'fcntl' +# +# == Overview +# +# dRuby is a distributed object system for Ruby. It is written in +# pure Ruby and uses its own protocol. No add-in services are needed +# beyond those provided by the Ruby runtime, such as TCP sockets. It +# does not rely on or interoperate with other distributed object +# systems such as CORBA, RMI, or .NET. +# +# dRuby allows methods to be called in one Ruby process upon a Ruby +# object located in another Ruby process, even on another machine. +# References to objects can be passed between processes. Method +# arguments and return values are dumped and loaded in marshalled +# format. All of this is done transparently to both the caller of the +# remote method and the object that it is called upon. +# +# An object in a remote process is locally represented by a +# DRb::DRbObject instance. This acts as a sort of proxy for the +# remote object. Methods called upon this DRbObject instance are +# forwarded to its remote object. This is arranged dynamically at run +# time. There are no statically declared interfaces for remote +# objects, such as CORBA's IDL. +# +# dRuby calls made into a process are handled by a DRb::DRbServer +# instance within that process. This reconstitutes the method call, +# invokes it upon the specified local object, and returns the value to +# the remote caller. Any object can receive calls over dRuby. There +# is no need to implement a special interface, or mixin special +# functionality. Nor, in the general case, does an object need to +# explicitly register itself with a DRbServer in order to receive +# dRuby calls. +# +# One process wishing to make dRuby calls upon another process must +# somehow obtain an initial reference to an object in the remote +# process by some means other than as the return value of a remote +# method call, as there is initially no remote object reference it can +# invoke a method upon. This is done by attaching to the server by +# URI. Each DRbServer binds itself to a URI such as +# 'druby://example.com:8787'. A DRbServer can have an object attached +# to it that acts as the server's *front* *object*. A DRbObject can +# be explicitly created from the server's URI. This DRbObject's +# remote object will be the server's front object. This front object +# can then return references to other Ruby objects in the DRbServer's +# process. +# +# Method calls made over dRuby behave largely the same as normal Ruby +# method calls made within a process. Method calls with blocks are +# supported, as are raising exceptions. In addition to a method's +# standard errors, a dRuby call may also raise one of the +# dRuby-specific errors, all of which are subclasses of DRb::DRbError. +# +# Any type of object can be passed as an argument to a dRuby call or +# returned as its return value. By default, such objects are dumped +# or marshalled at the local end, then loaded or unmarshalled at the +# remote end. The remote end therefore receives a copy of the local +# object, not a distributed reference to it; methods invoked upon this +# copy are executed entirely in the remote process, not passed on to +# the local original. This has semantics similar to pass-by-value. +# +# However, if an object cannot be marshalled, a dRuby reference to it +# is passed or returned instead. This will turn up at the remote end +# as a DRbObject instance. All methods invoked upon this remote proxy +# are forwarded to the local object, as described in the discussion of +# DRbObjects. This has semantics similar to the normal Ruby +# pass-by-reference. +# +# The easiest way to signal that we want an otherwise marshallable +# object to be passed or returned as a DRbObject reference, rather +# than marshalled and sent as a copy, is to include the +# DRb::DRbUndumped mixin module. +# +# dRuby supports calling remote methods with blocks. As blocks (or +# rather the Proc objects that represent them) are not marshallable, +# the block executes in the local, not the remote, context. Each +# value yielded to the block is passed from the remote object to the +# local block, then the value returned by each block invocation is +# passed back to the remote execution context to be collected, before +# the collected values are finally returned to the local context as +# the return value of the method invocation. +# +# == Examples of usage. +# +# For more dRuby samples, see the +samples+ directory in the full +# dRuby distribution. +# +# === dRuby in client/server mode. +# +# This illustrates setting up a simple client-server drb +# system. Run the server and client code in different terminals, +# starting the server code first. +# +# ==== Server code +# +# require 'drb/drb' +# +# # The URI for the server to connect to +# URI="druby://localhost:8787" +# +# class TimeServer +# +# def get_current_time +# return Time.now +# end +# +# end +# +# # The object that handles requests on the server +# FRONT_OBJECT=TimeServer.new +# +# $SAFE = 1 # disable eval() and friends +# +# DRb.start_service(URI, FRONT_OBJECT) +# # Wait for the drb server thread to finish before exiting. +# DRb.thread.join +# +# ==== Client code +# +# require 'drb/drb' +# +# # The URI to connect to +# SERVER_URI="druby://localhost:8787" +# +# # Start a local DRbServer to handle callbacks. +# # +# # Not necessary for this small example, but will be required +# # as soon as we pass a non-marshallable object as an argument +# # to a dRuby call. +# DRb.start_service +# +# timeserver = DRbObject.new_with_uri(SERVER_URI) +# puts timeserver.get_current_time +# +# === Remote objects under dRuby +# +# This example illustrates returning a reference to an object +# from a dRuby call. The Logger instances live in the server +# process. References to them are returned to the client process, +# where methods can be invoked upon them. These methods are +# executed in the server process. +# +# ==== Server code +# +# require 'drb/drb' +# +# URI="druby://localhost:8787" +# +# class Logger +# +# # Make dRuby send Logger instances as dRuby references, +# # not copies. +# include DRb::DRbUndumped +# +# def initialize(n, fname) +# @name = n +# @filename = fname +# end +# +# def log(message) +# File.open(@filename, "a") do |f| +# f.puts("#{Time.now}: #{@name}: #{message}") +# end +# end +# +# end +# +# # We have a central object for creating and retrieving loggers. +# # This retains a local reference to all loggers created. This +# # is so an existing logger can be looked up by name, but also +# # to prevent loggers from being garbage collected. A dRuby +# # reference to an object is not sufficient to prevent it being +# # garbage collected! +# class LoggerFactory +# +# def initialize(bdir) +# @basedir = bdir +# @loggers = {} +# end +# +# def get_logger(name) +# if !@loggers.has_key? name +# # make the filename safe, then declare it to be so +# fname = name.gsub(/[.\/]/, "_").untaint +# @loggers[name] = Logger.new(name, @basedir + "/" + fname) +# end +# return @loggers[name] +# end +# +# end +# +# FRONT_OBJECT=LoggerFactory.new("/tmp/dlog") +# +# $SAFE = 1 # disable eval() and friends +# +# DRb.start_service(URI, FRONT_OBJECT) +# DRb.thread.join +# +# ==== Client code +# +# require 'drb/drb' +# +# SERVER_URI="druby://localhost:8787" +# +# DRb.start_service +# +# log_service=DRbObject.new_with_uri(SERVER_URI) +# +# ["loga", "logb", "logc"].each do |logname| +# +# logger=log_service.get_logger(logname) +# +# logger.log("Hello, world!") +# logger.log("Goodbye, world!") +# logger.log("=== EOT ===") +# +# end +# +# == Security +# +# As with all network services, security needs to be considered when +# using dRuby. By allowing external access to a Ruby object, you are +# not only allowing outside clients to call the methods you have +# defined for that object, but by default to execute arbitrary Ruby +# code on your server. Consider the following: +# +# # !!! UNSAFE CODE !!! +# ro = DRbObject::new_with_uri("druby://your.server.com:8989") +# class << ro +# undef :instance_eval # force call to be passed to remote object +# end +# ro.instance_eval("`rm -rf *`") +# +# The dangers posed by instance_eval and friends are such that a +# DRbServer should generally be run with $SAFE set to at least +# level 1. This will disable eval() and related calls on strings +# passed across the wire. The sample usage code given above follows +# this practice. +# +# A DRbServer can be configured with an access control list to +# selectively allow or deny access from specified IP addresses. The +# main druby distribution provides the ACL class for this purpose. In +# general, this mechanism should only be used alongside, rather than +# as a replacement for, a good firewall. +# +# == dRuby internals +# +# dRuby is implemented using three main components: a remote method +# call marshaller/unmarshaller; a transport protocol; and an +# ID-to-object mapper. The latter two can be directly, and the first +# indirectly, replaced, in order to provide different behaviour and +# capabilities. +# +# Marshalling and unmarshalling of remote method calls is performed by +# a DRb::DRbMessage instance. This uses the Marshal module to dump +# the method call before sending it over the transport layer, then +# reconstitute it at the other end. There is normally no need to +# replace this component, and no direct way is provided to do so. +# However, it is possible to implement an alternative marshalling +# scheme as part of an implementation of the transport layer. +# +# The transport layer is responsible for opening client and server +# network connections and forwarding dRuby request across them. +# Normally, it uses DRb::DRbMessage internally to manage marshalling +# and unmarshalling. The transport layer is managed by +# DRb::DRbProtocol. Multiple protocols can be installed in +# DRbProtocol at the one time; selection between them is determined by +# the scheme of a dRuby URI. The default transport protocol is +# selected by the scheme 'druby:', and implemented by +# DRb::DRbTCPSocket. This uses plain TCP/IP sockets for +# communication. An alternative protocol, using UNIX domain sockets, +# is implemented by DRb::DRbUNIXSocket in the file drb/unix.rb, and +# selected by the scheme 'drbunix:'. A sample implementation over +# HTTP can be found in the samples accompanying the main dRuby +# distribution. +# +# The ID-to-object mapping component maps dRuby object ids to the +# objects they refer to, and vice versa. The implementation to use +# can be specified as part of a DRb::DRbServer's configuration. The +# default implementation is provided by DRb::DRbIdConv. It uses an +# object's ObjectSpace id as its dRuby id. This means that the dRuby +# reference to that object only remains meaningful for the lifetime of +# the object's process and the lifetime of the object within that +# process. A modified implementation is provided by DRb::TimerIdConv +# in the file drb/timeridconv.rb. This implementation retains a local +# reference to all objects exported over dRuby for a configurable +# period of time (defaulting to ten minutes), to prevent them being +# garbage-collected within this time. Another sample implementation +# is provided in sample/name.rb in the main dRuby distribution. This +# allows objects to specify their own id or "name". A dRuby reference +# can be made persistent across processes by having each process +# register an object using the same dRuby name. +# module DRb + + # Superclass of all errors raised in the DRb module. class DRbError < RuntimeError; end + + # Error raised when an error occurs on the underlying communication + # protocol. class DRbConnError < DRbError; end + # Class responsible for converting between an object and its id. + # + # This, the default implementation, uses an object's local ObjectSpace + # __id__ as its id. This means that an object's identification over + # drb remains valid only while that object instance remains alive + # within the server runtime. + # + # For alternative mechanisms, see DRb::TimerIdConv in rdb/timeridconv.rb + # and DRbNameIdConv in sample/name.rb in the full drb distribution. class DRbIdConv + + # Convert an object reference id to an object. + # + # This implementation looks up the reference id in the local object + # space and returns the object it refers to. def to_obj(ref) ObjectSpace._id2ref(ref) end + # Convert an object into a reference id. + # + # This implementation returns the object's __id__ in the local + # object space. def to_id(obj) obj.nil? ? nil : obj.__id__ end end + # Mixin module making an object undumpable or unmarshallable. + # + # If an object which includes this module is returned by method + # called over drb, then the object remains in the server space + # and a reference to the object is returned, rather than the + # object being marshalled and moved into the client space. module DRbUndumped - def _dump(dummy) + def _dump(dummy) # :nodoc: raise TypeError, 'can\'t dump' end end + # Error raised by the DRb module when an attempt is made to refer to + # the context's current drb server but the context does not have one. + # See #current_server. class DRbServerNotFound < DRbError; end + + # Error raised by the DRbProtocol module when it cannot find any + # protocol implementation support the scheme specified in a URI. class DRbBadURI < DRbError; end + + # Error raised by a dRuby protocol when it doesn't support the + # scheme specified in a URI. See DRb::DRbProtocol. class DRbBadScheme < DRbError; end + # An exception wrapping a DRb::DRbUnknown object class DRbUnknownError < DRbError + + # Create a new DRbUnknownError for the DRb::DRbUnknown object +unknown+ def initialize(unknown) @unknown = unknown super(unknown.name) end + + # Get the wrapped DRb::DRbUnknown object. attr_reader :unknown - def self._load(s) + def self._load(s) # :nodoc: Marshal::load(s) end - def _dump(lv) + def _dump(lv) # :nodoc: Marshal::dump(@unknown) end end + # Class wrapping a marshalled object whose type is unknown locally. + # + # If an object is returned by a method invoked over drb, but the + # class of the object is unknown in the client namespace, or + # the object is a constant unknown in the client namespace, then + # the still-marshalled object is returned wrapped in a DRbUnknown instance. + # + # If this object is passed as an argument to a method invoked over + # drb, then the wrapped object is passed instead. + # + # The class or constant name of the object can be read from the + # +name+ attribute. The marshalled object is held in the +buf+ + # attribute. class DRbUnknown + + # Create a new DRbUnknown object. + # + # +buf+ is a string containing a marshalled object that could not + # be unmarshalled. +err+ is the error message that was raised + # when the unmarshalling failed. It is used to determine the + # name of the unmarshalled object. def initialize(err, buf) case err when /uninitialized constant (\S+)/ @@ -60,9 +461,17 @@ module DRb end @buf = buf end - attr_reader :name, :buf - def self._load(s) + # The name of the unknown thing. + # + # Class name for unknown objects; variable name for unknown + # constants. + attr_reader :name + + # Buffer contained the marshalled, unknown object. + attr_reader :buf + + def self._load(s) # :nodoc: begin Marshal::load(s) rescue NameError, ArgumentError @@ -70,26 +479,41 @@ module DRb end end - def _dump(lv) + def _dump(lv) # :nodoc: @buf end + # Attempt to load the wrapped marshalled object again. + # + # If the class of the object is now known locally, the object + # will be unmarshalled and returned. Otherwise, a new + # but identical DRbUnknown object will be returned. def reload self.class._load(@buf) end + # Create a DRbUnknownError exception containing this object. def exception DRbUnknownError.new(self) end end + # Handler for sending and receiving drb messages. + # + # This takes care of the low-level marshalling and unmarshalling + # of drb requests and responses sent over the wire between server + # and client. This relieves the implementor of a new drb + # protocol layer with having to deal with these details. + # + # The user does not have to directly deal with this object in + # normal use. class DRbMessage - def initialize(config) + def initialize(config) # :nodoc: @load_limit = config[:load_limit] @argc_limit = config[:argc_limit] end - def dump(obj) + def dump(obj) # :nodoc: obj = DRbObject.new(obj) if obj.kind_of? DRbUndumped begin str = Marshal::dump(obj) @@ -99,7 +523,7 @@ module DRb [str.size].pack('N') + str end - def load(soc) + def load(soc) # :nodoc: sz = soc.read(4) # sizeof (N) raise(DRbConnError, 'connection closed') if sz.nil? raise(DRbConnError, 'premature header') if sz.size < 4 @@ -115,7 +539,7 @@ module DRb end end - def send_request(stream, ref, msg_id, arg, b) + def send_request(stream, ref, msg_id, arg, b) # :nodoc: ary = [] ary.push(dump(ref.__drbref)) ary.push(dump(msg_id.id2name)) @@ -127,7 +551,7 @@ module DRb stream.write(ary.join('')) end - def recv_request(stream) + def recv_request(stream) # :nodoc: ref = load(stream) ro = DRb.to_obj(ref) msg = load(stream) @@ -141,24 +565,98 @@ module DRb return ro, msg, argv, block end - def send_reply(stream, succ, result) + def send_reply(stream, succ, result) # :nodoc: stream.write(dump(succ) + dump(result)) end - def recv_reply(stream) + def recv_reply(stream) # :nodoc: succ = load(stream) result = load(stream) [succ, result] end end + # Module managing the underlying network protocol(s) used by drb. + # + # By default, drb uses the DRbTCPSocket protocol. Other protocols + # can be defined. A protocol must define the following class methods: + # + # [open(uri, config)] Open a client connection to the server at +uri+, + # using configuration +config+. Return a protocol + # instance for this connection. + # [open_server(uri, config)] Open a server listening at +uri+, + # using configuration +config+. Return a + # protocol instance for this listener. + # [uri_option(uri, config)] Take a URI, possibly containing an option + # component (e.g. a trailing '?param=val'), + # and return a [uri, option] tuple. + # + # All of these methods should raise a DRbBadScheme error if the URI + # does not identify the protocol they support (e.g. "druby:" for + # the standard Ruby protocol). This is how the DRbProtocol module, + # given a URI, determines which protocol implementation serves that + # protocol. + # + # The protocol instance returned by #open_server must have the + # following methods: + # + # [accept] Accept a new connection to the server. Returns a protocol + # instance capable of communicating with the client. + # [close] Close the server connection. + # [uri] Get the URI for this server. + # + # The protocol instance returned by #open must have the following methods: + # + # [send_request (ref, msg_id, arg, b)] + # Send a request to +ref+ with the given message id and arguments. + # This is most easily implemented by calling DRbMessage.send_request, + # providing a stream that sits on top of the current protocol. + # [recv_reply] + # Receive a reply from the server and return it as a [success-boolean, + # reply-value] pair. This is most easily implemented by calling + # DRb.recv_reply, providing a stream that sits on top of the + # current protocol. + # [alive?] + # Is this connection still alive? + # [close] + # Close this connection. + # + # The protocol instance returned by #open_server().accept() must have + # the following methods: + # + # [recv_request] + # Receive a request from the client and return a [object, message, + # args, block] tuple. This is most easily implemented by calling + # DRbMessage.recv_request, providing a stream that sits on top of + # the current protocol. + # [send_reply(succ, result)] + # Send a reply to the client. This is most easily implemented + # by calling DRbMessage.send_reply, providing a stream that sits + # on top of the current protocol. + # [close] + # Close this connection. + # + # A new protocol is registered with the DRbProtocol module using + # the add_protocol method. + # + # For examples of other protocols, see DRbUNIXSocket in drb/unix.rb, + # and HTTP0 in sample/http0.rb and sample/http0serv.rb in the full + # drb distribution. module DRbProtocol - module_function + + # Add a new protocol to the DRbProtocol module. def add_protocol(prot) @protocol.push(prot) end + module_function :add_protocol - module_function + # Open a client connection to +uri+ with the configuration +config+. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to open the URI. Each protocol signals that it does not handle that + # URIby raising a DRbBadScheme error. If no protocol recognises the + # URI, then a DRbBadURI error is raised. If a protocol accepts the + # URI, but an error occurs in opening it, a DRbConnError is raised. def open(uri, config, first=true) @protocol.each do |prot| begin @@ -176,8 +674,17 @@ module DRb end raise DRbBadURI, 'can\'t parse uri:' + uri end - - module_function + module_function :open + + # Open a server listening for connections at +uri+ with + # configuration +config+. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to open a server at the URI. Each protocol signals that it does + # not handle that URI by raising a DRbBadScheme error. If no protocol + # recognises the URI, then a DRbBadURI error is raised. If a protocol + # accepts the URI, but an error occurs in opening it, the underlying + # error is passed on to the caller. def open_server(uri, config, first=true) @protocol.each do |prot| begin @@ -191,8 +698,14 @@ module DRb end raise DRbBadURI, 'can\'t parse uri:' + uri end + module_function :open_server - module_function + # Parse +uri+ into a [uri, option] pair. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to parse the URI. Each protocol signals that it does not handle that + # URI by raising a DRbBadScheme error. If no protocol recognises the + # URI, then a DRbBadURI error is raised. def uri_option(uri, config, first=true) @protocol.each do |prot| begin @@ -208,15 +721,19 @@ module DRb end raise DRbBadURI, 'can\'t parse uri:' + uri end + module_function :uri_option - module_function - def auto_load(uri, config) + def auto_load(uri, config) # :nodoc: if uri =~ /^drb([a-z0-9]+):/ require("drb/#{$1}") rescue nil end end + module_function :auto_load end + # The default drb protocol. + # + # Communicates over a TCP socket. class DRbTCPSocket private def self.parse_uri(uri) @@ -232,6 +749,8 @@ module DRb end public + + # Open a client connection to +uri+ using configuration +config+. def self.open(uri, config) host, port, option = parse_uri(uri) host.untaint @@ -240,6 +759,8 @@ module DRb self.new(uri, soc, config) end + # Open a server listening for connections at +uri+ using + # configuration +config+. def self.open_server(uri, config) uri = 'druby://:0' unless uri host, port, opt = parse_uri(uri) @@ -254,11 +775,17 @@ module DRb self.new(uri, soc, config) end + # Parse +uri+ into a [uri, option] pair. def self.uri_option(uri, config) host, port, option = parse_uri(uri) return "druby://#{host}:#{port}", option end + # Create a new DRbTCPSocket instance. + # + # +uri+ is the URI we are connected to. + # +soc+ is the tcp socket we are bound to. +config+ is our + # configuration. def initialize(uri, soc, config={}) @uri = uri @socket = soc @@ -267,31 +794,47 @@ module DRb @msg = DRbMessage.new(config) set_sockopt(@socket) end + + # Get the URI that we are connected to. attr_reader :uri + # Get the address of our TCP peer (the other end of the socket + # we are bound to. def peeraddr @socket.peeraddr end + # Get the socket. def stream; @socket; end + # On the client side, send a request to the server. def send_request(ref, msg_id, arg, b) @msg.send_request(stream, ref, msg_id, arg, b) end + # On the server side, receive a request from the client. def recv_request @msg.recv_request(stream) end + # On the server side, send a reply to the client. def send_reply(succ, result) @msg.send_reply(stream, succ, result) end + # On the client side, receive a reply from the server. def recv_reply @msg.recv_reply(stream) end public + + # Close the connection. + # + # If this is an instance returned by #open_server, then this stops + # listening for new connections altogether. If this is an instance + # returned by #open or by #accept, then it closes this particular + # client-server session. def close if @socket @socket.close @@ -299,6 +842,9 @@ module DRb end end + # On the server side, for an instance returned by #open_server, + # accept a client connection and return a new instance to handle + # the server's side of this client-server session. def accept while true s = @socket.accept @@ -308,6 +854,7 @@ module DRb self.class.new(nil, s, @config) end + # Check to see if this connection is alive. def alive? return false unless @socket if IO.select([@socket], nil, nil, 0) @@ -317,7 +864,7 @@ module DRb true end - def set_sockopt(soc) + def set_sockopt(soc) # :nodoc: soc.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) soc.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) if defined? Fcntl::O_NONBLOCK soc.fcntl(Fcntl::F_SETFL, Fcntl::FD_CLOEXEC) if defined? Fcntl::FD_CLOEXEC @@ -328,7 +875,7 @@ module DRb @protocol = [DRbTCPSocket] # default end - class DRbURIOption + class DRbURIOption # :nodoc: I don't understand the purpose of this class... def initialize(option) @option = option.to_s end @@ -347,7 +894,17 @@ module DRb alias eql? == end + # Object wrapping a reference to a remote drb object. + # + # Method calls on this object are relayed to the remote + # object that this object is a stub for. class DRbObject + + # Unmarshall a marshalled DRbObject. + # + # If the referenced object is located within the local server, then + # the object itself is returned. Otherwise, a new DRbObject is + # created to act as a stub for the remote referenced object. def self._load(s) uri, ref = Marshal.load(s) if DRb.here?(uri) @@ -359,14 +916,23 @@ module DRb it end + # Create a new DRbObject from a URI alone. def self.new_with_uri(uri) self.new(nil, uri) end + # Marshall this object. + # + # The URI and ref of the object are marshalled. def _dump(lv) Marshal.dump([@uri, @ref]) end + # Create a new remote object stub. + # + # +obj+ is the (local) object we want to create a stub for. Normally + # this is +nil+. +uri+ is the URI of the remote object that this + # will be a stub for. def initialize(obj, uri=nil) @uri = nil @ref = nil @@ -380,15 +946,18 @@ module DRb end end + # Reinitialise this object with the given +uri+ and +ref+ def reinit(uri, ref) @uri = uri @ref = ref end - def __drburi + # Get the URI of the remote object. + def __drburi @uri end + # Get the reference of the object, if local. def __drbref @ref end @@ -397,6 +966,7 @@ module DRb undef :to_a undef :respond_to? + # Routes method calls to the referenced object. def method_missing(msg_id, *a, &b) if DRb.here?(@uri) obj = DRb.to_obj(@ref) @@ -426,12 +996,21 @@ module DRb end end + # Class handling the connection between a DRbObject and the + # server the real object lives on. + # + # This class maintains a pool of connections, to reduce the + # overhead of starting and closing down connections for each + # method call. + # + # This class is used internally by DRbObject. The user does + # not normally need to deal with it directly. class DRbConn - POOL_SIZE = 16 + POOL_SIZE = 16 # :nodoc: @mutex = Mutex.new @pool = [] - def self.open(remote_uri) + def self.open(remote_uri) # :nodoc: begin conn = nil @@ -463,27 +1042,38 @@ module DRb end end - def initialize(remote_uri) + def initialize(remote_uri) # :nodoc: @uri = remote_uri @protocol = DRbProtocol.open(remote_uri, DRb.config) end - attr_reader :uri + attr_reader :uri # :nodoc: - def send_message(ref, msg_id, arg, block) + def send_message(ref, msg_id, arg, block) # :nodoc: @protocol.send_request(ref, msg_id, arg, block) @protocol.recv_reply end - def close + def close # :nodoc: @protocol.close @protocol = nil end - def alive? + def alive? # :nodoc: @protocol.alive? end end + # Class representing a drb server instance. + # + # A DRbServer must be running in the local process before any incoming + # dRuby calls can be accepted, or any local objects can be passed as + # dRuby references to remote processes, even if those local objects are + # never actually called remotely. You do not need to start a DRbServer + # in the local process if you are only making outgoing dRuby calls + # passing marshalled parameters. + # + # Unless multiple servers are being used, the local DRbServer is normally + # started by calling DRb.start_service. class DRbServer @@acl = nil @@idconv = DRbIdConv.new @@ -492,31 +1082,47 @@ module DRb @@load_limit = 256 * 102400 @@verbose = false + # Set the default value for the :argc_limit option. + # + # See #new(). The initial default value is 256. def self.default_argc_limit(argc) @@argc_limit = argc end + # Set the default value for the :load_limit option. + # + # See #new(). The initial default value is 25 MB. def self.default_load_limit(sz) @@load_limit = sz end + # Set the default value for the :acl option. + # + # See #new(). The initial default value is nil. def self.default_acl(acl) @@acl = acl end + # Set the default value for the :id_conv option. + # + # See #new(). The initial default value is a DRbIdConv instance. def self.default_id_conv(idconv) @@idconv = idconv end + # Set the default value of the :verbose option. + # + # See #new(). The initial default value is false. def self.verbose=(on) @@verbose = on end + # Get the default value of the :verbose option. def self.verbose @@verbose end - def self.make_config(hash={}) + def self.make_config(hash={}) # :nodoc: default_config = { :idconv => @@idconv, :verbose => @@verbose, @@ -527,6 +1133,49 @@ module DRb default_config.update(hash) end + # Create a new DRbServer instance. + # + # +uri+ is the URI to bind to. This is normally of the form + # 'druby://:' where is a hostname of + # the local machine. If nil, then the system's default hostname + # will be bound to, on a port selected by the system; these value + # can be retrieved from the +uri+ attribute. 'druby:' specifies + # the default dRuby transport protocol: another protocol, such + # as 'drbunix:', can be specified instead. + # + # +front+ is the front object for the server, that is, the object + # to which remote method calls on the server will be passed. If + # nil, then the server will not accept remote method calls. + # + # If +config_or_acl+ is a hash, it is the configuration to + # use for this server. The following options are recognised: + # + # :idconv :: an id-to-object conversion object. This defaults + # to an instance of the class DRb::DRbIdConv. + # :verbose :: if true, all unsucessful remote calls on objects + # in the server will be logged to $stdout. false + # by default. + # :tcp_acl :: the access control list for this server. See + # the ACL class from the main dRuby distribution. + # :load_limit :: the maximum message size in bytes accepted by + # the server. Defaults to 25 MB (26214400). + # :argc_limit :: the maximum number of arguments to a remote + # method accepted by the server. Defaults to + # 256. + # + # The default values of these options can be modified on + # a class-wide basis by the class methods #default_argc_limit, + # #default_load_limit, #default_acl, #default_id_conv, + # and #verbose= + # + # If +config_or_acl+ is not a hash, but is not nil, it is + # assumed to be the access control list for this server. + # See the :tcp_acl option for more details. + # + # If no other server is currently set as the primary server, + # this will become the primary server. + # + # The server will immediately start running in its own thread. def initialize(uri=nil, front=nil, config_or_acl=nil) if Hash === config_or_acl config = config_or_acl.dup @@ -552,26 +1201,54 @@ module DRb DRb.primary_server = self unless DRb.primary_server end end - attr_reader :uri, :thread, :front + + # The URI of this DRbServer. + attr_reader :uri + + # The main thread of this DRbServer. + # + # This is the thread that listens for and accepts connections + # from clients, not that handles each client's request-response + # session. + attr_reader :thread + + # The front object of the DRbServer. + # + # This object receives remote method calls made on the server's + # URI alone, with an object id. + attr_reader :front + + # The configuration of this DRbServer attr_reader :config + # Set whether to operate in verbose mode. + # + # In verbose mode, failed calls are logged to stdout. def verbose=(v); @config[:verbose]=v; end + + # Get whether the server is in verbose mode. + # + # In verbose mode, failed calls are logged to stdout. def verbose; @config[:verbose]; end + # Is this server alive? def alive? @thread.alive? end + # Stop this server. def stop_service @thread.kill end + # Convert a dRuby reference to the local object it refers to. def to_obj(ref) return front if ref.nil? return front[ref.to_s] if DRbURIOption === ref @idconv.to_obj(ref) end + # Convert a local object to a dRuby reference. def to_id(obj) return nil if obj.__id__ == front.__id__ @idconv.to_id(obj) @@ -605,17 +1282,32 @@ module DRb end end + # List of insecure methods. + # + # These methods are not callable via dRuby. INSECURE_METHOD = [ :__send__ ] + + # Has a method been included in the list of insecure methods? def insecure_method?(msg_id) INSECURE_METHOD.include?(msg_id) end + # Coerce an object to a string, providing our own representation if + # to_s is not defined for the object. def any_to_s(obj) obj.to_s rescue sprintf("#<%s:0x%lx>", obj.class, obj.__id__) end + # Check that a method is callable via dRuby. + # + # +obj+ is the object we want to invoke the method on. +msg_id+ is the + # method name, as a Symbol. + # + # If the method is an insecure method (see #insecure_method?) a + # SecurityError is thrown. If the method is private or undefined, + # a NameError is thrown. def check_insecure_method(obj, msg_id) return true if Proc === obj && msg_id == :__drb_yield raise(ArgumentError, "#{any_to_s(msg_id)} is not a symbol") unless Symbol == msg_id.class @@ -636,7 +1328,7 @@ module DRb end public :check_insecure_method - class InvokeMethod + class InvokeMethod # :nodoc: def initialize(drb_server, client) @drb_server = drb_server @client = client @@ -703,6 +1395,13 @@ module DRb end end + # The main loop performed by a DRbServer's internal thread. + # + # Accepts a connection from a client, and starts up its own + # thread to handle it. This thread loops, receiving requests + # from the client, invoking them on a local object, and + # returning responses, until the client closes the connection + # or a local method call fails. def main_loop Thread.start(@protocol.accept) do |client| @grp.add Thread.current @@ -733,14 +1432,42 @@ module DRb @primary_server = nil + # Start a dRuby server locally. + # + # The new dRuby server will become the primary server, even + # if another server is currently the primary server. + # + # +uri+ is the URI for the server to bind to. If nil, + # the server will bind to random port on the default local host + # name and use the default dRuby protocol. + # + # +front+ is the server's front object. This may be nil. + # + # +config+ is the configuration for the new server. This may + # be nil. + # + # See DRbServer::new. def start_service(uri=nil, front=nil, config=nil) @primary_server = DRbServer.new(uri, front, config) end module_function :start_service + # The primary local dRuby server. + # + # This is the server created by the #start_service call. attr_accessor :primary_server module_function :primary_server=, :primary_server + # Get the 'current' server. + # + # In the context of execution taking place within the main + # thread of a dRuby server (typically, as a result of a remote + # call on the server or one of its objects), the current + # server is that server. Otherwise, the current server is + # the primary server. + # + # If the above rule fails to find a server, a DRbServerNotFound + # error is raised. def current_server drb = Thread.current['DRb'] server = (drb && drb['server']) ? drb['server'] : @primary_server @@ -749,22 +1476,34 @@ module DRb end module_function :current_server + # Stop the local dRuby server. + # + # This operates on the primary server. If there is no primary + # server currently running, it is a noop. def stop_service @primary_server.stop_service if @primary_server @primary_server = nil end module_function :stop_service + # Get the URI defining the local dRuby space. + # + # This is the URI of the current server. See #current_server. def uri current_server.uri end module_function :uri + # Is +uri+ the URI for the current local server? def here?(uri) (current_server.uri rescue nil) == uri end module_function :here? + # Get the configuration of the current server. + # + # If there is no current server, this returns the default configuration. + # See #current_server and DRbServer::make_config. def config current_server.config rescue @@ -772,30 +1511,52 @@ module DRb end module_function :config + # Get the front object of the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. def front current_server.front end module_function :front + # Convert a reference into an object using the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. def to_obj(ref) current_server.to_obj(ref) end + + # Get a reference id for an object using the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. def to_id(obj) current_server.to_id(obj) end module_function :to_id module_function :to_obj + # Get the thread of the primary server. + # + # This returns nil if there is no primary server. See #primary_server. def thread @primary_server ? @primary_server.thread : nil end module_function :thread + # Set the default id conv object. + # + # See DRbServer#default_id_conv. def install_id_conv(idconv) DRbServer.default_id_conv(idconv) end module_function :install_id_conv + # Set the default acl. + # + # See DRb::DRbServer.default_acl. def install_acl(acl) DRbServer.default_acl(acl) end -- cgit v1.2.3