On Tue, Mar 11, 2003 at 08:48:09AM +0900, Hal E. Fulton wrote: > > I would really like the 'extend ThreadSafe' to perform the initialisation > of > > the instance variable automatically though. Is there a way to do that? > > I suppose you could add a line to > __send__: > > if not @__tsmutex then @__tsmutex = Mutex.new end or shorter: @__tsmutex ||= Mutex.new > But then that slows down every method call by a > few more microseconds. More importantly, it's not thread-safe for the creation of the Mutex itself. I guess it's unlikely that this race would cause a problem, but in theory it could. I like Kent's suggestion of using the extend_object trigger at the point where the Module is included, this wasn't something I was aware of. In the end, because I needed a pattern which distributed requests amongst multiple objects, I came up with the attached solution. It lets you create a pool of objects, and you can pass the object representing the pool to a DRb server (or a SOAP server, or any server which runs concurrent threads on the same object). Incoming method calls are parcelled out to the individual objects, and no object handles more than one request at a time. I'd be interested in any comments on the attached solution (especially flaws that I've overlooked); if it is sound then I'll stick it up on the Wiki. Regards, Brian. =begin = Synopsis require 'facade' def Foo def initialize @ok = true end def doit(x) puts "Doing #{x}, I am #{id}" sleep 2 puts "Done #{x} by #{id}" end def suicide; @ok = false; end def alive?; @ok; end end pool = Facade::SimplePool.new(5, :doit, :suicide) { Foo.new } DRb.start_service('druby://0.0.0.0:9000', pool) = Description Lets you set up a thread-safe pool of objects. Method calls to the pool are delegated to one of its objects, and only one method is allowed to run on each object at any time. If the object responds to an 'alive?' message this is used to check whether it is still usable. If it returns nil or false, then a fresh object is created to replace it in the pool. = Class Methods --- Facade::SimplePool.new( count, methods... ) { factoryblock } Constructs a pool of 'count' objects, calling the factoryblock to create each one. If necessary, you can list the methods which you wish to make available (this is necessary for DRb, which refuses calls to methods which are not explicitly included in the object) =end require 'thread' module Facade class SimplePool def initialize(n, *methodlist, &factory) @n = n @factory = factory @mutex = Mutex.new @poolnotempty = ConditionVariable.new @pool = (1..n).collect { @factory.call } if methodlist methodlist.each do |m| eval "class <<self; def #{m}(*a); method_missing(:#{m},*a); end; end" end end end def method_missing(meth, *argary) item = rc = nil argary = [argary] unless argary.kind_of? Array @mutex.synchronize do @poolnotempty.wait(@mutex) if @pool.size == 0 item = @pool.shift end if item.respond_to?(:alive?) && !item.alive? item = @factory.call end begin rc = item.__send__(meth, *argary) ensure @mutex.synchronize do @pool.push item @poolnotempty.signal end end rc end def alive? # don't proxy this to a random object in the pool true end end end