Fork process with DRb client

Hello,

If I fork a (Linux) process with a DRb client, both parent and child
will communicate with the DRb server using the same Unix Domain
sockets. With predictable, nasty results.

Is there a way for the child to somhow destroy its DRb::DRbObject and
create a new ?

I've scanned the source, but found nothing useful.

Any suggestions ?

Cheers,

Han Holl

this seems ok:

   harp:~ > cat a.rb
   require 'drb'
   require 'tmpdir'
   require 'yaml'

   STDOUT.sync = STDERR.sync = true

···

On Fri, 23 Jun 2006, Han Holl wrote:

Hello,

If I fork a (Linux) process with a DRb client, both parent and child will
communicate with the DRb server using the same Unix Domain sockets. With
predictable, nasty results.

Is there a way for the child to somhow destroy its DRb::DRbObject and create
a new ?

I've scanned the source, but found nothing useful.

Any suggestions ?

Cheers,

Han Holl

   #
   # setup unix domain drb server in another process
   #
     r, w = IO.pipe
     uri = nil
     cid = fork

     unless cid
       r.close
       sok = File.join Dir.tmpdir, Process.pid.to_s
       at_exit{ File.unlink sok }
       uri = "drbunix://#{ sok }"
       (list = []).extend DRbUndumped
       DRb.start_service uri, 'children' => list, 'parent' => nil
       w.puts uri
       Thread.new(Thread.current) do |cur|
         begin
           w.puts "no zombies"
         rescue Exception
           cur.exit
         end
       end
       DRb.thread.join
     else
       w.close
       uri = r.gets.strip
     end
   #
   # attach a client to it
   #
     DRb.start_service
     dhash = DRbObject.new nil, uri
     dhash['parent'] = Process.pid
   #
   # dump info
   #
     y 'uri' => uri
     y 'parent' => dhash['parent']
     y 'children' => dhash['children'].map
   #
   # fork a few children - each one tears down and sets up client again
   #
     3.times do
       cid = fork
       unless cid
         DRb.stop_service
         DRb.start_service
         dhash = DRbObject.new nil, uri
         dhash['children'] << Process.pid
         exit
       else
         Process.wait
       end
     end
   #
   # dump info
   #
     y 'uri' => uri
     y 'parent' => dhash['parent']
     y 'children' => dhash['children'].map

   harp:~ > ruby a.rb
   uri: drbunix:///tmp/15163
   parent: 15162
   children: []

   uri: drbunix:///tmp/15163
   parent: 15162
   children:
   - 15165
   - 15167
   - 15169

regards.

-a
--
suffering increases your inner strength. also, the wishing for suffering
makes the suffering disappear.
- h.h. the 14th dali lama

[ cut]

   #
   # fork a few children - each one tears down and sets up client again
   #
     3.times do
       cid = fork
       unless cid
         DRb.stop_service
         DRb.start_service
         dhash = DRbObject.new nil, uri
         dhash['children'] << Process.pid
         exit
       else
         Process.wait

[ cut]
Hi Ara,

The problem with this approach is that you have to know the uri of the
server at fork time, which can be a problem.
(And I didn't realize that you can destroy a DRbObject with
DRb.stop_service -- I associate that with the server side of things).

I've found a mote generic way of forking a process that is a DRb
client, but this has as drawback that it has an initimate knowlegde of
the way DRb is implemented.
module DRb
  class DRbConn
    def self.fork
      f = @mutex.synchronize do
        Process::fork
      end
      if f.nil?
        @pool = []
        if block_given?
          yield
          exit 0
        end
      end
      f
    end
  end
end

This just deletes the commection pool in the child.
Forking within the mutex is because it's possible to fork with some
other thread having the mutex locked, which causes problems for the
one-thread child.

But it's a bit of a kludge and I would prefer it if DRbObject had a
method to deal with it.

Thanks,

Han Holl

···

On 6/23/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:

hi han-

The problem with this approach is that you have to know the uri of the
server at fork time, which can be a problem.

it shouldn't be, you can do, for example

   uri = drbobject.__drburi

eg. if you have a drb object you always have it's uri. if you have it's uri
you can always detach and re-attach. though i agree it's not that elegant.

(And I didn't realize that you can destroy a DRbObject with
DRb.stop_service -- I associate that with the server side of things).

yeah, it seems like it. remember though, every ruby program which uses drb is
a servant - drb is always both server and client. fyi.

I've found a mote generic way of forking a process that is a DRb client, but
this has as drawback that it has an initimate knowlegde of the way DRb is
implemented.

module DRb
class DRbConn
  def self.fork
    f = @mutex.synchronize do
      Process::fork
    end
    if f.nil?
      @pool = []
      if block_given?
        yield
        exit 0
      end
    end
    f
  end
end

This just deletes the commection pool in the child. Forking within the
mutex is because it's possible to fork with some other thread having the
mutex locked, which causes problems for the one-thread child.

doesn't it leave open file handles lying around?

But it's a bit of a kludge and I would prefer it if DRbObject had a method
to deal with it.

agreed. it's always tough to carry open file handles across a fork -
sometimes is just seems easier to code around it. for instance, by setting up
a ring server where each service registers in the parent. then all children
simply grab drbobjects of of that ring server. maybe something like this
might apply to your problem:

   harp:~ > ruby a.rb
   [[:key, :val]]
   [:val]

   harp:~ > cat a.rb
   require 'drb'
   require 'rinda/ring'
   require 'rinda/tuplespace'
   require 'tmpdir'
   require 'yaml'

   STDOUT.sync = STDERR.sync = true
   def start_ring_server
     Rinda::RingServer.new Rinda::TupleSpace.new
   end
   def find_ring_server
     Rinda::TupleSpaceProxy.new Rinda::RingFinger::new.lookup_ring_any
   end
   def drb_join
     begin; DRb.thread.join; rescue Exception; exit; end
   end
   children = []

···

On Sat, 24 Jun 2006, Han Holl wrote:

   #
   # one process starts a ring server. this could also be the parent. other
   # processes, or even the parent, can register drb objects with this ring
   # server.
   #
     children <<
       fork{
         DRb.start_service
         rs = start_ring_server
         drb_join
       }

   #
   # this one starts up a hash server and registers it's location
   #
     children <<
       fork{
         (dhash = {}).extend DRbUndumped
         DRb.start_service nil, dhash
         rs = find_ring_server
         rs.write [:dhash, dhash]
         drb_join
       }

   #
   # this one starts up an array server and registers it's location
   #
     children <<
       fork{
         (darray = []).extend DRbUndumped
         DRb.start_service nil, darray
         rs = find_ring_server
         rs.write [:darray, darray]
         drb_join
       }

   #
   # now any child, or the parent, can find and use these drb objects without
   # knowing their locations or requiring a connection before forking - only
   # the 'name' of the object need be known.
   #
     Process.waitpid fork{
       DRb.start_service
       rs = find_ring_server

       tuple = rs.read [:dhash, nil]
       dhash = tuple.last
       dhash[:key] = :val

       tuple = rs.read [:darray, nil]
       darray = tuple.last
       darray << :val
     }

   #
   # here the parent finds the dhash and darray to print them out
   #
     DRb.start_service
     rs = find_ring_server

     tuple = rs.read [:dhash, nil]
     dhash = tuple.last

     tuple = rs.read [:darray, nil]
     darray = tuple.last

     p dhash.map #=> [[:key, :val]]
     p darray.map #=> [:val]

   #
   # wait for children
   #
     at_exit{
       children.each{|cid| Process.kill 'TERM', cid rescue next}
       loop{ Process.wait Process::WNOHANG rescue break }
     }
     STDIN.gets

regards.

-a
--
suffering increases your inner strength. also, the wishing for suffering
makes the suffering disappear.
- h.h. the 14th dali lama