class Forkner

Forkner

Constants

VERSION

version 1.2

Attributes

max[RW]

The maximum number of child processes to run at once.

tmp_dir[RW]

Temporary directory for storing information from child processes. Defaults to /tmp.

Public Class Methods

container(max) { |forkner| ... } click to toggle source

container is probably the easiest way to use Forkner. Run all the code that will have child processes inside a container block. The single parameter for container is the maximum number of child processes to allow. After the container block, Forkner waits for all remaining child processes to exit.

So, for example, this code loops 100 times, but will not fork more than five children at a time:

Forkner.container(5) do |forkner|
    100.times do
        forkner.child do
            # do stuff in the child process
        end
    end
end
# File lib/forkner.rb, line 42
def self.container(max)
        forkner = Forkner.new(max)
        yield forkner
        forkner.wait_all
end
new(max) click to toggle source

Creates a new Forkner object. The single parameter is the maximum number of child processes to run at once. So, for example, the following code creates a Forkner object that will allow up to five child processes at once.

forkner = Forkner.new(5)
# File lib/forkner.rb, line 63
def initialize(max)
        @max = max
        @children = {}
        @tmp_dir = '/tmp'
        @reaper = nil
end

Public Instance Methods

child() { || ... } click to toggle source

Runs a child process. If there are already the maximum number of children running then this method waits until one of them exits. The last line of the block is information that can be stored in JSON, and if you have defined a reaper block, then that information will be conveyed back to the parent process.

For example, this child process generates a random number and a timestamp. The last line of the block is a hash that will be sent back to the parent process.

forkner.child do
    myrand = rand()
    timestamp = Time.now
    {'myrand'=>myrand, 'timestamp'=>timestamp}
end
# File lib/forkner.rb, line 94
def child
        # init
        json_path = nil
        
        # transfer file
        if @reaper
                json_path = Random.rand().to_s
                json_path = json_path.sub(/\A.*\./mu, '')
                json_path = @tmp_dir + '/' + json_path
        end
        
        # wait until we have a space for a process
        waiter @max - 1
        
        # parent process
        if new_child_pid = Process.fork()
                # set child record
                child = @children[new_child_pid] = {}
                
                # file handle
                if @reaper
                        child['json_path'] = json_path
                end
                
                # return true
                return true
        
        # child process
        else
                # yield if necessary
                if block_given?
                        rv = yield()
                        
                        # save to json file if necessary
                        if json_path
                                File.write json_path, JSON.generate(rv)
                        end
                        
                        # exit child process
                        exit
                end
                
                # always return false
                return false
        end
end
reaper(&block) click to toggle source

Defines the block to run when a child process finishes. The single param passed to the block is a hash or array of information from the child process. For example, if the child process returns a hash of information, you might display it like this:

forkner.reaper() do |rv|
    puts '-----'
    puts rv['myrand']
    puts rv['timestamp']
end
# File lib/forkner.rb, line 160
def reaper(&block)
        @reaper = block
end
wait_all() click to toggle source

Waits for all child processes to finish. You don't need to call this method if you're using Forkner.container.

# File lib/forkner.rb, line 175
def wait_all
        waiter 0
end
Also aliased as: waitall
waitall()

waitall is just an alias for wait_all because I can never remember whether or not there's an underscore in the method.

Alias for: wait_all

Private Instance Methods

waiter(wait_max) click to toggle source
# File lib/forkner.rb, line 196
def waiter(wait_max)
        # loop until we have fewer than @max children
        while @children.length > wait_max
                begin
                        # wait
                        old_child_pid = Process.wait(-1, Process::WNOHANG)
                        old_child = @children.delete(old_child_pid)
                        
                        # if child
                        if old_child and old_child['json_path']
                                # if reaper
                                if @reaper
                                        # slurp in JSON
                                        rv = JSON.parse(File.read(old_child['json_path']))
                                        
                                        # delete transfer file
                                        File.unlink old_child['json_path']
                                        
                                        # call reaper
                                        @reaper.call rv
                                end
                        end
                        
                # TODO: Handle system errors
                rescue SystemCallError
                end
        end
end