From: Brian Candler Date: 2003-03-21T03:18:04+09:00 Subject: Re: Quick DRb question... --n8g4imXOkfNTN/H1 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Fri, Mar 21, 2003 at 02:41:23AM +0900, Chris Pine wrote: > In DRb (or drb, or druby, or... what should I call it?), I'm starting a > server and my Apache/mod_ruby scripts are connecting to it when they need > to. > > My question: If two (or more) people connect to my webserver at roughly the > same time, on two different Apache/mod_ruby processes, are their requests > all sent to the same DRb object, one at a time? They are sent to the same DRb object, and may be processed at the *same* time. DRb starts a new thread for handling each incoming request. You have to serialise the requests yourself, or build your own pool of objects if that's how you want them handled. A sample is attached below, if it's any use. Cheers, Brian. --n8g4imXOkfNTN/H1 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="simplepool.rb" =begin = Synopsis require 'pool' class Foo def doit(x) puts "Doing #{x}, I am #{id}" sleep 2 puts "Done #{x} by #{id}" end end pool = Pool::SimplePool.new(5) { Foo.new } obj = Pool::Facade.new(pool, :doit) require 'drb' DRb.start_service('druby://0.0.0.0:9001', obj) = Description A SimplePool is a thread-safe pool of objects. You can 'get' an item from the pool, and it is not available again until it has been 'put' back in (or the block associated with 'get' has been run) A Facade is an object which relays method calls to an arbitrary object in the pool. = Class Methods --- SimplePool.new( start=1, [max, check, exception] ) { factoryblock } Constructs a pool of objects, calling the factoryblock to create each one. Whenever the pool is empty it may create additional objects, up to the limit of 'max' objects active at any one time. Use max=nil for unlimited instances. = Instance Methods --- SimplePool#get { block } Retrieve an item from the pool, execute the associated block passing the item as a parameter, and then put it back into the pool. When the pool is created, it can be given two extra parameters: 'check' is a method name, and 'exception' is an exception class (defaulting to StandardError) If the execution of the block raises an error, then the object's 'check' method is called. If it returns false/nil, or raises an exception, then the object is considered dead and is not returned to the pool. The original exception is still returned to the caller. Example: class Foo initialize @db = DBI::connect('dbi:mysql:whatever') end alive? @db.select_one("select 4+5")[0] == 9 # connection sanity check end end pool = Pool::SimplePool(5, 5, :alive?) { Foo.new } You can force objects to be destroyed after every exception by giving 'true' as the method name (or any non-symbol), e.g. pool = Pool::SimplePool(1, 10, true, Exception) --- SimplePool#get ==> returns obj --- SimplePool#put( obj ) Methods to remove an item from the pool, and return it to the pool. put nil if you want to permanently discard it rather than return it (this allows a subsequent 'get' to create a new instance) = Class Methods --- Facade.new( pool, [methodnames] ) Constructs a new facade object for the given pool. If methnames are given, then explicit methods are added to the facade for those methods. This is necessary for DRb where method_missing does not work. = Instance Methods --- Facade#( ) Pick an item from the pool and send it the given method/args, after which it is returned to the pool. =end require 'thread' module Pool class SimplePool def initialize(initial=1, max=initial, checkmeth=nil, err=StandardError, &factory) @max = max @checkmeth = checkmeth @err = err @factory = factory @poolaccess = Mutex.new @poolnotempty = ConditionVariable.new @createobj = Mutex.new @pool = (1..initial).collect { @factory.call } @numobjs = initial end def get item = nil @poolaccess.synchronize do if @pool.size > 0 item = @pool.shift elsif !@max || @numobjs < @max @numobjs += 1 # we will create it outside of this mutex else @poolnotempty.wait(@poolaccess) item = @pool.shift end end # Don't create object under the poolaccess mutex, as that would block # out other fetch/put operations. But do serialise it under a separate # mutex, as it may not be thread-safe (e.g. if it uses class variables) unless item @createobj.synchronize do item = @factory.call end end return item unless block_given? begin result = yield item rescue @err => boom if @checkmeth begin item = nil unless item.send(@checkmeth) rescue Exception item = nil end end raise boom ensure put item end result end def put(item) @poolaccess.synchronize do if item @pool.push item @poolnotempty.signal else @numobjs -= 1 end end nil end def totalsize @numobjs # approx (changes asynchronously) end def freesize @pool.size # approx (changes asynchronously) end end class Facade def initialize(pool, *methodlist) @pool = pool methodlist.each do |m| eval "class <