Class: ZTK::Background

Inherits:
Base
  • Object
show all
Defined in:
lib/ztk/background.rb

Overview

Background Processing Class

This class can be used to easily run a linear process in a background manner.

The before fork callback is called once in the parent process.

The after fork callback is called twice, once in the parent process and once in the child process.

Examples:

Run a background process which simply sleeps illustrating the callback hooks:


a_callback = Proc.new do |pid|
  puts "Hello from After Callback - PID #{pid}"
end

b_callback = Proc.new do |pid|
  puts "Hello from Before Callback - PID #{pid}"
end

background = ZTK::Background.new
background.config do |config|
  config.before_fork = b_callback
  config.after_fork = a_callback
end

pid = background.process do
  sleep(1)
end
puts pid.inspect

background.wait
puts background.result.inspect

Author:

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from Base

build_config, #config, #direct_log, hash_config, log_and_raise, #log_and_raise

Constructor Details

- (Background) initialize(configuration = {})

Returns a new instance of Background

Parameters:

  • configuration (Hash) (defaults to: {})

    Configuration options hash.



51
52
53
54
55
56
# File 'lib/ztk/background.rb', line 51

def initialize(configuration={})
  super(configuration)

  @result = nil
  GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
end

Instance Attribute Details

- (Object) pid

Result Set



47
48
49
# File 'lib/ztk/background.rb', line 47

def pid
  @pid
end

- (Object) result

Result Set



47
48
49
# File 'lib/ztk/background.rb', line 47

def result
  @result
end

Instance Method Details

- (Boolean) alive?

Returns:

  • (Boolean)


128
129
130
# File 'lib/ztk/background.rb', line 128

def alive?
  (Process.getpgid(@pid).is_a?(Integer) rescue false)
end

- (Boolean) dead?

Returns:

  • (Boolean)


132
133
134
# File 'lib/ztk/background.rb', line 132

def dead?
  !alive?
end

- (Integer) process { ... }

Process in background.

Yields:

  • Block should execute tasks to be performed in background.

Yield Returns:

  • (Object)

    Block can return any object to be marshalled back to the parent processes.

Returns:

  • (Integer)

    Returns the pid of the child process forked.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ztk/background.rb', line 65

def process(&block)
  !block_given? and log_and_raise(BackgroundError, "You must supply a block to the process method!")

  @child_reader, @parent_writer = IO.pipe
  @parent_reader, @child_writer = IO.pipe

  config.before_fork and config.before_fork.call(Process.pid)
  @pid = Process.fork do
    config.after_fork and config.after_fork.call(Process.pid)

    @parent_writer.close
    @parent_reader.close

    STDOUT.reopen("/dev/null", "a")
    STDERR.reopen("/dev/null", "a")
    STDIN.reopen("/dev/null")

    if !(data = block.call).nil?
      config.ui.logger.debug { "write(#{data.inspect})" }
      @child_writer.write(Base64.encode64(Marshal.dump(data)))
    end

    @child_reader.close
    @child_writer.close
    Process.exit!(0)
  end
  config.after_fork and config.after_fork.call(Process.pid)

  @child_reader.close
  @child_writer.close

  @pid
end

- (Array<pid, status, data>) wait

Wait for the background process to finish.

If a process successfully finished, it's return value from the process block is stored into the result set.

It's advisable to use something like the at_exit hook to ensure you don't leave orphaned processes. For example, in the at_exit hook you could call wait to block until the child process finishes up.

Returns:

  • (Array<pid, status, data>)

    An array containing the pid, status and data returned from the process block. If wait2() fails nil is returned.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/ztk/background.rb', line 112

def wait
  config.ui.logger.debug { "wait" }
  pid, status = (Process.wait2(@pid) rescue nil)
  if !pid.nil? && !status.nil?
    data = (Marshal.load(Base64.decode64(@parent_reader.read.to_s)) rescue nil)
    config.ui.logger.debug { "read(#{data.inspect})" }
    !data.nil? and @result = data

    @parent_reader.close
    @parent_writer.close

    return [pid, status, data]
  end
  nil
end