A
Ara.T.Howard
SYNOPSIS
the Slave class forks a process and starts a drb server in the child using
any object as the server. the process is detached so it is not required
(nor possible) to wait on the child pid. a Heartbeat is set up between the
parent and child processes so that the child will exit of the parent exits
for any reason - preventing orphaned slaves from running indefinitely. the
purpose of Slaves is to be able to easily set up a collection of objects
communicating via drb protocols instead of having to use IPC.
typical usage:
slave = Slave::new{ AnyObject.new }
slave.object #=> handle on drb object
slave.uri #=> uri of the drb object
slave.socket #=> unix domain socket path for drb object
slave.psname #=> title shown in ps/top
object = slave.object
value = object.any_method #=> use the object normally
slaves may be configured via the environment, the Slave class, or via the
ctor for object itself. attributes which may be configured include
* object : specify the slave object. otherwise block value is used.
* socket_creation_attempts : specify how many attempts to create a unix domain socket will be made
* debug : turn on some logging to STDERR
* psname : specify the name that will appear in 'top' ($0)
* at_exit : specify a lambda to be called in the *parent* when the child dies
* dumped : specify that the slave object should *not* be DRbUndumped (default is DRbUndumped)
* threadsafe : wrap the slave object with ThreadSafe to implement gross thread safety
URIS
http://rubyforge.org/projects/codeforpeople/
http://codeforpeople.com/lib/ruby/slave
HISTORY
1.1.0:
- replaced HeartBeat class with LifeLine.
- __HUGE__ cleanup of file descriptor/fork management with tons of help
from skaar and ezra. thanks guys!
- introduced Slave.object method used to return any object directory from
a child process. see samples/g.rb.
- indroduced keyword to automatically make slave objects threadsafe.
remember that your slave object must be threadsafe because they are
being served via DRb!!!
SAMPLES
<========< samples/a.rb >========>
~ > cat samples/a.rb
require 'slave'
#
# simple usage is simply to stand up a server object as a slave. you do not
# need to wait for the server, join it, etc. it will die when the parent
# process dies - even under 'kill -9' conditions
#
class Server
def add_two n
n + 2
end
end
slave = Slave.new bject => Server.new
server = slave.object
p server.add_two(40) #=> 42
slave.shutdown
~ > ruby samples/a.rb
42
<========< samples/b.rb >========>
~ > cat samples/b.rb
require 'slave'
#
# if certain operations need to take place in the child only a block can be
# used
#
class Server
def connect_to_db
"we only want to do this in the child process!"
@connection = ostgresql
end
attr :connection
end
slave = Slave.new('object' => Server.new){|s| s.connect_to_db}
server = slave.object
p server.connection #=> ostgresql
#
# errors in the child are detected and raised in the parent
#
slave = Slave.new('object' => Server.new){|s| s.typo} #=> raises an error!
~ > ruby samples/b.rb
ostgresql
./lib/slave.rb:460:in `initialize': undefined method `typo' for #<Server:0xb7565694> (NoMethodError)
from samples/b.rb:22:in `new'
from samples/b.rb:22
<========< samples/c.rb >========>
~ > cat samples/c.rb
require 'slave'
#
# if no slave object is given the block itself is used to contruct it
#
class Server
def initialize
"this is run only in the child"
@pid = Process.pid
end
attr 'pid'
end
slave = Slave.new{ Server.new }
server = slave.object
p Process.pid
p server.pid # not going to be the same as parents!
#
# errors are still detected though
#
slave = Slave.new{ fubar } # raises error in parent
~ > ruby samples/c.rb
14387
14388
./lib/slave.rb:460:in `initialize': undefined local variable or method `fubar' for main:Object (NameError)
from samples/c.rb:21:in `new'
from samples/c.rb:21
<========< samples/d.rb >========>
~ > cat samples/d.rb
require 'slave'
#
# at_exit hanlders are handled correctly in both child and parent
#
at_exit{ p 'parent' }
slave = Slave.new{ at_exit{ p 'child' }; 'the server is this string' }
#
# this will print 'child', then 'parent'
#
~ > ruby samples/d.rb
"child"
"parent"
<========< samples/e.rb >========>
~ > cat samples/e.rb
require 'slave'
#
# slaves never outlive their parent. if the parent exits, even under kill -9,
# the child will die.
#
slave = Slave.new{ at_exit{ p 'child' }; 'the server is this string' }
Process.kill brutal=9, the_parent_pid=Process.pid
#
# even though parent dies a nasty death the child will still print 'child'
#
~ > ruby samples/e.rb
"child"
<========< samples/f.rb >========>
~ > cat samples/f.rb
require 'slave'
#
# slaves created previously are visible to newly created slaves - in this
# example the child process of slave_a communicates directly with the child
# process of slave_a
#
slave_a = Slave.new{ Array.new }
slave_b = Slave.new{ slave_a.object }
a, b = slave_b.object, slave_a.object
b << 42
puts a #=> 42
~ > ruby samples/f.rb
42
<========< samples/g.rb >========>
~ > cat samples/g.rb
require 'slave'
#
# Slave.object can used when you want to construct an object in another
# process. in otherwords you want to fork a process and retrieve a single
# returned object from that process as opposed to setting up a server.
#
this = Process.pid
that = Slave.object{ Process.pid }
p 'this' => this, 'that' => that
#
# any object can be returned and it can be returned asychronously via a thread
#
thread = Slave.objectasync => true){ sleep 2 and [ Process.pid, Time.now ] }
this = [ Process.pid, Time.now ]
that = thread.value
p 'this' => this, 'that' => that
~ > ruby samples/g.rb
{"that"=>14406, "this"=>14405}
{"that"=>[14407, Tue Nov 28 09:47:31 MST 2006], "this"=>[14405, Tue Nov 28 09:47:29 MST 2006]}
enjoy.
-a
the Slave class forks a process and starts a drb server in the child using
any object as the server. the process is detached so it is not required
(nor possible) to wait on the child pid. a Heartbeat is set up between the
parent and child processes so that the child will exit of the parent exits
for any reason - preventing orphaned slaves from running indefinitely. the
purpose of Slaves is to be able to easily set up a collection of objects
communicating via drb protocols instead of having to use IPC.
typical usage:
slave = Slave::new{ AnyObject.new }
slave.object #=> handle on drb object
slave.uri #=> uri of the drb object
slave.socket #=> unix domain socket path for drb object
slave.psname #=> title shown in ps/top
object = slave.object
value = object.any_method #=> use the object normally
slaves may be configured via the environment, the Slave class, or via the
ctor for object itself. attributes which may be configured include
* object : specify the slave object. otherwise block value is used.
* socket_creation_attempts : specify how many attempts to create a unix domain socket will be made
* debug : turn on some logging to STDERR
* psname : specify the name that will appear in 'top' ($0)
* at_exit : specify a lambda to be called in the *parent* when the child dies
* dumped : specify that the slave object should *not* be DRbUndumped (default is DRbUndumped)
* threadsafe : wrap the slave object with ThreadSafe to implement gross thread safety
URIS
http://rubyforge.org/projects/codeforpeople/
http://codeforpeople.com/lib/ruby/slave
HISTORY
1.1.0:
- replaced HeartBeat class with LifeLine.
- __HUGE__ cleanup of file descriptor/fork management with tons of help
from skaar and ezra. thanks guys!
- introduced Slave.object method used to return any object directory from
a child process. see samples/g.rb.
- indroduced keyword to automatically make slave objects threadsafe.
remember that your slave object must be threadsafe because they are
being served via DRb!!!
SAMPLES
<========< samples/a.rb >========>
~ > cat samples/a.rb
require 'slave'
#
# simple usage is simply to stand up a server object as a slave. you do not
# need to wait for the server, join it, etc. it will die when the parent
# process dies - even under 'kill -9' conditions
#
class Server
def add_two n
n + 2
end
end
slave = Slave.new bject => Server.new
server = slave.object
p server.add_two(40) #=> 42
slave.shutdown
~ > ruby samples/a.rb
42
<========< samples/b.rb >========>
~ > cat samples/b.rb
require 'slave'
#
# if certain operations need to take place in the child only a block can be
# used
#
class Server
def connect_to_db
"we only want to do this in the child process!"
@connection = ostgresql
end
attr :connection
end
slave = Slave.new('object' => Server.new){|s| s.connect_to_db}
server = slave.object
p server.connection #=> ostgresql
#
# errors in the child are detected and raised in the parent
#
slave = Slave.new('object' => Server.new){|s| s.typo} #=> raises an error!
~ > ruby samples/b.rb
ostgresql
./lib/slave.rb:460:in `initialize': undefined method `typo' for #<Server:0xb7565694> (NoMethodError)
from samples/b.rb:22:in `new'
from samples/b.rb:22
<========< samples/c.rb >========>
~ > cat samples/c.rb
require 'slave'
#
# if no slave object is given the block itself is used to contruct it
#
class Server
def initialize
"this is run only in the child"
@pid = Process.pid
end
attr 'pid'
end
slave = Slave.new{ Server.new }
server = slave.object
p Process.pid
p server.pid # not going to be the same as parents!
#
# errors are still detected though
#
slave = Slave.new{ fubar } # raises error in parent
~ > ruby samples/c.rb
14387
14388
./lib/slave.rb:460:in `initialize': undefined local variable or method `fubar' for main:Object (NameError)
from samples/c.rb:21:in `new'
from samples/c.rb:21
<========< samples/d.rb >========>
~ > cat samples/d.rb
require 'slave'
#
# at_exit hanlders are handled correctly in both child and parent
#
at_exit{ p 'parent' }
slave = Slave.new{ at_exit{ p 'child' }; 'the server is this string' }
#
# this will print 'child', then 'parent'
#
~ > ruby samples/d.rb
"child"
"parent"
<========< samples/e.rb >========>
~ > cat samples/e.rb
require 'slave'
#
# slaves never outlive their parent. if the parent exits, even under kill -9,
# the child will die.
#
slave = Slave.new{ at_exit{ p 'child' }; 'the server is this string' }
Process.kill brutal=9, the_parent_pid=Process.pid
#
# even though parent dies a nasty death the child will still print 'child'
#
~ > ruby samples/e.rb
"child"
<========< samples/f.rb >========>
~ > cat samples/f.rb
require 'slave'
#
# slaves created previously are visible to newly created slaves - in this
# example the child process of slave_a communicates directly with the child
# process of slave_a
#
slave_a = Slave.new{ Array.new }
slave_b = Slave.new{ slave_a.object }
a, b = slave_b.object, slave_a.object
b << 42
puts a #=> 42
~ > ruby samples/f.rb
42
<========< samples/g.rb >========>
~ > cat samples/g.rb
require 'slave'
#
# Slave.object can used when you want to construct an object in another
# process. in otherwords you want to fork a process and retrieve a single
# returned object from that process as opposed to setting up a server.
#
this = Process.pid
that = Slave.object{ Process.pid }
p 'this' => this, 'that' => that
#
# any object can be returned and it can be returned asychronously via a thread
#
thread = Slave.objectasync => true){ sleep 2 and [ Process.pid, Time.now ] }
this = [ Process.pid, Time.now ]
that = thread.value
p 'this' => this, 'that' => that
~ > ruby samples/g.rb
{"that"=>14406, "this"=>14405}
{"that"=>[14407, Tue Nov 28 09:47:31 MST 2006], "this"=>[14405, Tue Nov 28 09:47:29 MST 2006]}
enjoy.
-a