class RFlow::ChildProcess

Encapsulates a child process being managed by RFlow.

Constants

SIGINFO

Symbolic constant for SIGINFO as this is only defined on BSD and not in Ruby.

Attributes

name[R]

The name of the child process. @return [String]

pid[R]

The PID of the child process. @return [Fixnum]

Public Class Methods

new(name, role = name) click to toggle source

@param name [String] process name @param role [String] role to be played by the process, for logging (Master, Broker, etc.)

# File lib/rflow/child_process.rb, line 18
def initialize(name, role = name)
  @name = name
  @role = role
end

Public Instance Methods

shutdown!(signal) click to toggle source

Called when the child process needs to be shut down, before it dies. Clears signal handlers. @param signal [String] SIG*, whichever signal caused the shutdown @return [void]

# File lib/rflow/child_process.rb, line 65
def shutdown!(signal)
  RFlow.logger.info "Shutting down due to #{signal}"
  unhandle_signals
end
spawn!() click to toggle source

Launch another process to execute the child. The parent process retains the original worker object (with pid and IPC pipe) to allow for process management. Parent will return once the child starts; child will update its process name, detach from the process group, set up signal handlers, and execute {run_child_process}; when that returns, it will exit with return code 0. @return [void]

# File lib/rflow/child_process.rb, line 31
def spawn!
  establish_child_pipe
  drop_database_connections

  @pid = fork
  if @pid
    return_after_child_starts
  else
    run_child_process
  end
end

Protected Instance Methods

run_child_process() click to toggle source
# File lib/rflow/child_process.rb, line 44
def run_child_process
  @child_pipe_w.close
  register_logging_context
  update_process_name
  detach_process_group
  handle_signals

  RFlow.logger.info "#{@role} started"
  run_process
  exit 0
ensure
  unhandle_signals
end
run_process() click to toggle source
# File lib/rflow/child_process.rb, line 58
def run_process; end

Private Instance Methods

detach_process_group() click to toggle source

detach from parent process group so shutdown remains orderly (prevent signals from being delivered to entire group)

# File lib/rflow/child_process.rb, line 99
def detach_process_group
  Process.setpgid(0, 0)
end
drop_database_connections() click to toggle source

Holding database connections over the fork causes problems. Instead, let them be automatically restored after the fork.

# File lib/rflow/child_process.rb, line 78
def drop_database_connections
  ::ActiveRecord::Base.clear_all_connections!
end
establish_child_pipe() click to toggle source
# File lib/rflow/child_process.rb, line 71
def establish_child_pipe
  @child_pipe_r, @child_pipe_w = IO.pipe
  [@child_pipe_r, @child_pipe_w].each {|io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
end
handle_signals() click to toggle source
# File lib/rflow/child_process.rb, line 103
def handle_signals
  Signal.trap 'SIGCHLD', 'DEFAULT' # make sure child process can run subshells

  ['SIGTERM', 'SIGINT', 'SIGQUIT', 'SIGHUP'].each do |signal|
    trap_signal(signal) do
      shutdown! signal
      exit! 0
    end
  end

  trap_signal 'SIGUSR1' do
    RFlow.logger.reopen
  end

  trap_signal 'SIGUSR2' do
    RFlow.logger.toggle_log_level
  end

  trap_signal SIGINFO do
    RFlow.logger.dump_threads
  end
end
register_logging_context() click to toggle source
# File lib/rflow/child_process.rb, line 87
def register_logging_context
  # arrange for child's name to appear in log messages
  RFlow.logger.add_logging_context sprintf("%-#{RFlow.logger.context_width}s", @name)
end
return_after_child_starts() click to toggle source
# File lib/rflow/child_process.rb, line 82
def return_after_child_starts
  @child_pipe_r.close
  self
end
trap_signal(signal) { || ... } click to toggle source
# File lib/rflow/child_process.rb, line 132
def trap_signal(signal)
  # Log4r and traps don't mix, so we need to put it in another thread
  context = RFlow.logger.clone_logging_context
  Signal.trap signal do
    Thread.new do
      RFlow.logger.apply_logging_context context
      yield
    end.join
  end
end
unhandle_signals() click to toggle source
# File lib/rflow/child_process.rb, line 126
def unhandle_signals
  ['SIGTERM', 'SIGINT', 'SIGQUIT', 'SIGHUP', 'SIGCHLD', 'SIGUSR1', 'SIGUSR2', SIGINFO].each do |signal|
    Signal.trap signal, 'DEFAULT'
  end
end
update_process_name() click to toggle source
# File lib/rflow/child_process.rb, line 92
def update_process_name
  # set the visible process name to match the child's name
  $0 += " #{@name}"
end