ruby--ruby/lib/drb/drb.rb

808 lines
16 KiB
Ruby
Raw Normal View History

=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
require 'socket'
require 'thread'
require 'fcntl'
module DRb
class DRbError < RuntimeError; end
class DRbConnError < DRbError; end
class DRbIdConv
def to_obj(ref)
ObjectSpace._id2ref(ref)
end
def to_id(obj)
obj.nil? ? nil : obj.__id__
end
end
module DRbUndumped
def _dump(dummy)
raise TypeError, 'can\'t dump'
end
end
class DRbServerNotFound < DRbError; end
class DRbBadURI < DRbError; end
class DRbBadScheme < DRbError; end
class DRbUnknownError < DRbError
def initialize(unknown)
@unknown = unknown
super(unknown.name)
end
attr_reader :unknown
def self._load(s)
Marshal::load(s)
end
def _dump(lv)
Marshal::dump(@unknown)
end
end
class DRbUnknown
def initialize(err, buf)
case err
when /uninitialized constant (\S+)/
@name = $1
when /undefined class\/module (\S+)/
@name = $1
else
@name = nil
end
@buf = buf
end
attr_reader :name, :buf
def self._load(s)
begin
Marshal::load(s)
rescue NameError, ArgumentError
DRbUnknown.new($!, s)
end
end
def _dump(lv)
@buf
end
def reload
self.class._load(@buf)
end
def exception
DRbUnknownError.new(self)
end
end
class DRbMessage
def initialize(config)
@load_limit = config[:load_limit]
@argc_limit = config[:argc_limit]
end
def dump(obj)
obj = DRbObject.new(obj) if obj.kind_of? DRbUndumped
begin
str = Marshal::dump(obj)
rescue
str = Marshal::dump(DRbObject.new(obj))
end
[str.size].pack('N') + str
end
def load(soc)
sz = soc.read(4) # sizeof (N)
raise(DRbConnError, 'connection closed') if sz.nil?
raise(DRbConnError, 'premature header') if sz.size < 4
sz = sz.unpack('N')[0]
raise(DRbConnError, "too large packet #{sz}") if @load_limit < sz
str = soc.read(sz)
raise(DRbConnError, 'connection closed') if sz.nil?
raise(DRbConnError, 'premature marshal format(can\'t read)') if str.size < sz
begin
Marshal::load(str)
rescue NameError, ArgumentError
DRbUnknown.new($!, str)
end
end
def send_request(stream, ref, msg_id, arg, b)
ary = []
ary.push(dump(ref.__drbref))
ary.push(dump(msg_id.id2name))
ary.push(dump(arg.length))
arg.each do |e|
ary.push(dump(e))
end
ary.push(dump(b))
stream.write(ary.join(''))
end
def recv_request(stream)
ref = load(stream)
ro = DRb.to_obj(ref)
msg = load(stream)
argc = load(stream)
raise ArgumentError, 'too many arguments' if @argc_limit < argc
argv = Array.new(argc, nil)
argc.times do |n|
argv[n] = load(stream)
end
block = load(stream)
return ro, msg, argv, block
end
def send_reply(stream, succ, result)
stream.write(dump(succ) + dump(result))
end
def recv_reply(stream)
succ = load(stream)
result = load(stream)
[succ, result]
end
end
module DRbProtocol
module_function
def add_protocol(prot)
@protocol.push(prot)
end
module_function
def open(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open(uri, config)
rescue DRbBadScheme
rescue DRbConnError
raise($!)
rescue
raise(DRbConnError, "#{uri} - #{$!.inspect}")
end
end
if first && (config[:auto_load] != false)
auto_load(uri, config)
return open(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function
def open_server(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open_server(uri, config)
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri, config)
return open_server(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function
def uri_option(uri, config, first=true)
@protocol.each do |prot|
begin
uri, opt = prot.uri_option(uri, config)
# opt = nil if opt == ''
return uri, opt
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri, config)
return uri_option(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function
def auto_load(uri, config)
if uri =~ /^drb([a-z0-9]+):/
require("drb/#{$1}") rescue nil
end
end
end
class DRbTCPSocket
private
def self.parse_uri(uri)
if uri =~ /^druby:\/\/(.*?):(\d+)(\?(.*))?$/
host = $1
port = $2.to_i
option = $4
[host, port, option]
else
raise(DRbBadScheme, uri) unless uri =~ /^druby:/
raise(DRbBadURI, 'can\'t parse uri:' + uri)
end
end
public
def self.open(uri, config)
host, port, option = parse_uri(uri)
host.untaint
port.untaint
soc = TCPSocket.open(host, port)
self.new(uri, soc, config)
end
def self.open_server(uri, config)
uri = 'druby://:0' unless uri
host, port, opt = parse_uri(uri)
if host.size == 0
soc = TCPServer.open(port)
host = Socket.gethostname
else
soc = TCPServer.open(host, port)
end
port = soc.addr[1] if port == 0
uri = "druby://#{host}:#{port}"
self.new(uri, soc, config)
end
def self.uri_option(uri, config)
host, port, option = parse_uri(uri)
return "druby://#{host}:#{port}", option
end
def initialize(uri, soc, config={})
@uri = uri
@socket = soc
@config = config
@acl = config[:tcp_acl]
@msg = DRbMessage.new(config)
set_sockopt(@socket)
end
attr_reader :uri
def peeraddr
@socket.peeraddr
end
def stream; @socket; end
def send_request(ref, msg_id, arg, b)
@msg.send_request(stream, ref, msg_id, arg, b)
end
def recv_request
@msg.recv_request(stream)
end
def send_reply(succ, result)
@msg.send_reply(stream, succ, result)
end
def recv_reply
@msg.recv_reply(stream)
end
public
def close
if @socket
@socket.close
@socket = nil
end
end
def accept
while true
s = @socket.accept
break if (@acl ? @acl.allow_socket?(s) : true)
s.close
end
self.class.new(nil, s, @config)
end
def alive?
return false unless @socket
if IO.select([@socket], nil, nil, 0)
close
return false
end
true
end
def set_sockopt(soc)
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
end
end
module DRbProtocol
@protocol = [DRbTCPSocket] # default
end
class DRbURIOption
def initialize(option)
@option = option.to_s
end
attr :option
def to_s; @option; end
def ==(other)
return false unless DRbURIOption === other
@option == other.option
end
def hash
@option.hash
end
alias eql? ==
end
class DRbObject
def self._load(s)
uri, ref = Marshal.load(s)
if DRb.here?(uri)
return DRb.to_obj(ref)
end
it = self.new(nil)
it.reinit(uri, ref)
it
end
def self.new_with_uri(uri)
self.new(nil, uri)
end
def _dump(lv)
Marshal.dump([@uri, @ref])
end
def initialize(obj, uri=nil)
@uri = nil
@ref = nil
if obj.nil?
return if uri.nil?
@uri, option = DRbProtocol.uri_option(uri, DRb.config)
@ref = DRbURIOption.new(option) unless option.nil?
else
@uri = uri ? uri : (DRb.uri rescue nil)
@ref = obj ? DRb.to_id(obj) : nil
end
end
def reinit(uri, ref)
@uri = uri
@ref = ref
end
def __drburi
@uri
end
def __drbref
@ref
end
undef :to_s
undef :to_a
undef :respond_to?
def method_missing(msg_id, *a, &b)
if DRb.here?(@uri)
obj = DRb.to_obj(@ref)
DRb.current_server.check_insecure_method(obj, msg_id)
return obj.__send__(msg_id, *a, &b)
end
succ, result = DRbConn.open(@uri) do |conn|
conn.send_message(self, msg_id, a, b)
end
return result if succ
unless DRbUnknown === result
prefix = "(#{@uri}) "
bt = []
result.backtrace.each do |x|
break if /`__send__'$/ =~ x
if /^\(druby:\/\// =~ x
bt.push(x)
else
bt.push(prefix + x)
end
end
raise result, result.message, bt + caller
else
raise result
end
end
end
class DRbConn
POOL_SIZE = 16
@mutex = Mutex.new
@pool = []
def self.open(remote_uri)
begin
conn = nil
@mutex.synchronize do
#FIXME
new_pool = []
@pool.each do |c|
if conn.nil? and c.uri == remote_uri
conn = c if c.alive?
else
new_pool.push c
end
end
@pool = new_pool
end
conn = self.new(remote_uri) unless conn
succ, result = yield(conn)
return succ, result
ensure
@mutex.synchronize do
if @pool.size > POOL_SIZE or ! succ
conn.close if conn
else
@pool.unshift(conn)
end
end
end
end
def initialize(remote_uri)
@uri = remote_uri
@protocol = DRbProtocol.open(remote_uri, DRb.config)
end
attr_reader :uri
def send_message(ref, msg_id, arg, block)
@protocol.send_request(ref, msg_id, arg, block)
@protocol.recv_reply
end
def close
@protocol.close
@protocol = nil
end
def alive?
@protocol.alive?
end
end
class DRbServer
@@acl = nil
@@idconv = DRbIdConv.new
@@secondary_server = nil
@@argc_limit = 256
@@load_limit = 256 * 102400
@@verbose = false
def self.default_argc_limit(argc)
@@argc_limit = argc
end
def self.default_load_limit(sz)
@@load_limit = sz
end
def self.default_acl(acl)
@@acl = acl
end
def self.default_id_conv(idconv)
@@idconv = idconv
end
def self.verbose=(on)
@@verbose = on
end
def self.verbose
@@verbose
end
def self.make_config(hash={})
default_config = {
:idconv => @@idconv,
:verbose => @@verbose,
:tcp_acl => @@acl,
:load_limit => @@load_limit,
:argc_limit => @@argc_limit
}
default_config.update(hash)
end
def initialize(uri=nil, front=nil, config_or_acl=nil)
if Hash === config_or_acl
config = config_or_acl.dup
else
acl = config_or_acl || @@acl
config = {
:tcp_acl => acl
}
end
@config = self.class.make_config(config)
@protocol = DRbProtocol.open_server(uri, @config)
@uri = @protocol.uri
@front = front
@idconv = @config[:idconv]
@grp = ThreadGroup.new
@thread = run
Thread.exclusive do
DRb.primary_server = self unless DRb.primary_server
end
end
attr_reader :uri, :thread, :front
attr_reader :config
def verbose=(v); @config[:verbose]=v; end
def verbose; @config[:verbose]; end
def alive?
@thread.alive?
end
def stop_service
@thread.kill
end
def to_obj(ref)
return front if ref.nil?
return front[ref.to_s] if DRbURIOption === ref
@idconv.to_obj(ref)
end
def to_id(obj)
return nil if obj.__id__ == front.__id__
@idconv.to_id(obj)
end
private
def kill_sub_thread
Thread.new do
grp = ThreadGroup.new
grp.add(Thread.current)
list = @grp.list
while list.size > 0
list.each do |th|
th.kill if th.alive?
end
list = @grp.list
end
end
end
def run
Thread.start do
begin
while true
main_loop
end
ensure
@protocol.close if @protocol
kill_sub_thread
end
end
end
INSECURE_METHOD = [
:__send__
]
def insecure_method?(msg_id)
INSECURE_METHOD.include?(msg_id)
end
def any_to_s(obj)
obj.to_s rescue sprintf("#<%s:0x%lx>", obj.class, obj.__id__)
end
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
raise(SecurityError, "insecure method `#{msg_id}'") if insecure_method?(msg_id)
unless obj.respond_to?(msg_id)
desc = any_to_s(obj)
if desc.nil? || desc[0] == '#'
desc << ":#{obj.class}"
end
if obj.private_methods.include?(msg_id.to_s)
raise NameError, "private method `#{msg_id}' called for #{desc}"
else
raise NameError, "undefined method `#{msg_id}' called for #{desc}"
end
end
true
end
public :check_insecure_method
class InvokeMethod
def initialize(drb_server, client)
@drb_server = drb_server
@client = client
end
def perform
@result = nil
@succ = false
setup_message
if @block
@result = perform_with_block
else
@result = perform_without_block
end
@succ = true
return @succ, @result
rescue StandardError, ScriptError, Interrupt
@result = $!
return @succ, @result
end
private
def init_with_client
obj, msg, argv, block = @client.recv_request
@obj = obj
@msg_id = msg.intern
@argv = argv
@block = block
end
def check_insecure_method
@drb_server.check_insecure_method(@obj, @msg_id)
end
def setup_message
init_with_client
check_insecure_method
end
def perform_without_block
if Proc === @obj && @msg_id == :__drb_yield
if @argv.size == 1
ary = @argv
else
ary = [@argv]
end
ary.collect(&@obj)[0]
else
@obj.__send__(@msg_id, *@argv)
end
end
end
if RUBY_VERSION >= '1.8'
require 'drb/invokemethod'
class InvokeMethod
include InvokeMethod18Mixin
end
else
require 'drb/invokemethod16'
class InvokeMethod
include InvokeMethod16Mixin
end
end
def main_loop
Thread.start(@protocol.accept) do |client|
@grp.add Thread.current
Thread.current['DRb'] = { 'client' => client ,
'server' => self }
loop do
begin
succ = false
invoke_method = InvokeMethod.new(self, client)
succ, result = invoke_method.perform
if !succ && verbose
p result
result.backtrace.each do |x|
puts x
end
end
client.send_reply(succ, result) rescue nil
ensure
unless succ
client.close
return
end
end
end
end
end
end
@primary_server = nil
def start_service(uri=nil, front=nil, config=nil)
@primary_server = DRbServer.new(uri, front, config)
end
module_function :start_service
attr_accessor :primary_server
module_function :primary_server=, :primary_server
def current_server
drb = Thread.current['DRb']
server = (drb && drb['server']) ? drb['server'] : @primary_server
raise DRbServerNotFound unless server
return server
end
module_function :current_server
def stop_service
@primary_server.stop_service if @primary_server
@primary_server = nil
end
module_function :stop_service
def uri
current_server.uri
end
module_function :uri
def here?(uri)
(current_server.uri rescue nil) == uri
end
module_function :here?
def config
current_server.config
rescue
DRbServer.make_config
end
module_function :config
def front
current_server.front
end
module_function :front
def to_obj(ref)
current_server.to_obj(ref)
end
def to_id(obj)
current_server.to_id(obj)
end
module_function :to_id
module_function :to_obj
def thread
@primary_server ? @primary_server.thread : nil
end
module_function :thread
def install_id_conv(idconv)
DRbServer.default_id_conv(idconv)
end
module_function :install_id_conv
def install_acl(acl)
DRbServer.default_acl(acl)
end
module_function :install_acl
end
DRbObject = DRb::DRbObject
DRbUndumped = DRb::DRbUndumped
DRbIdConv = DRb::DRbIdConv