class Covalence::PopenWrapper

Public Class Methods

logger() click to toggle source
# File lib/covalence/core/cli_wrappers/popen_wrapper.rb, line 57
def logger
  Covalence::LOGGER
end
run(cmds, path, args, stdin_io: STDIN, stdout_io: STDOUT, stderr_io: STDERR, debug: Covalence::DEBUG_CLI, dry_run: false, ignore_exitcode: false, workdir: nil) click to toggle source
# File lib/covalence/core/cli_wrappers/popen_wrapper.rb, line 10
def run(cmds, path, args,
        stdin_io: STDIN,
        stdout_io: STDOUT,
        stderr_io: STDERR,
        debug: Covalence::DEBUG_CLI,
        dry_run: false,
        ignore_exitcode: false,
        workdir: nil)

  # TODO: implement path prefix for the docker runs, see @tf_cmd
  cmd_string = [*cmds]
  # TODO: cmd escape issues with -var.
  cmd_string += [*args] unless args.blank?
  cmd_string << path unless workdir

  #TODO debug command string maybe
  #TODO debug command args maybe

  run_cmd = cmd_string.join(' ')
  print_cmd_string(run_cmd)
  if dry_run
    run_cmd = Covalence::DRY_RUN_CMD
  end

  if debug
    return 0 unless HighLine.new.agree('Execute? [y/n]')
  end

  if workdir
    spawn_subprocess(ENV, run_cmd,
                     stdin_io: stdin_io,
                     stdout_io: stdout_io,
                     stderr_io: stderr_io,
                     ignore_exitcode: ignore_exitcode,
                     path: path,
                     workdir: workdir)
  else
    spawn_subprocess(ENV, run_cmd,
                     stdin_io: stdin_io,
                     stdout_io: stdout_io,
                     stderr_io: stderr_io,
                     ignore_exitcode: ignore_exitcode,
                     path: path)
  end

end

Private Class Methods

handle_io_streams(mappings, stdin_io) click to toggle source
# File lib/covalence/core/cli_wrappers/popen_wrapper.rb, line 100
def handle_io_streams(mappings, stdin_io)
  inputs = mappings.keys
  streams_ready_for_eof_check = []

  until inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io) do

    readable_inputs, _ = IO.select(inputs, [], [])
    streams_ready_for_eof_check = readable_inputs

    streams_ready_for_eof_check.select(&:eof).each do |src|
      Covalence::LOGGER.debug "Stopping redirection from an IO in EOF: " + src.inspect
      # `select`ing an IO which has reached EOF blocks forever.
      # So you have to delete such IO from the array of IOs to `select`.
      inputs.delete src

      # You must close the child process' STDIN immeditely after the parent's STDIN reached EOF,
      # or some kinds of child processes never exit.
      # e.g.) echo foobar | joumae run -- cat
      # After the `echo` finished outputting `foobar`, you have to tell `cat` about that or `cat` will wait for more inputs forever.
      mappings[src].close if src == stdin_io
    end

    break if inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io)

    readable_inputs.each do |input|
      begin
        data = input.read_nonblock(1024)
        output = mappings[input]
        output.write(data)
        output.flush
      rescue EOFError => e
        Covalence::LOGGER.debug "Reached EOF: #{e}"
        inputs.delete input
      rescue Errno::EPIPE => e
        Covalence::LOGGER.debug "Handled error: #{e}: io: #{input.inspect}"
        inputs.delete input
      end
    end #readable_inputs
  end #until inputs.empty?
end
print_cmd_string(cmd_string) click to toggle source
spawn_subprocess(env, run_cmd, stdin_io: STDIN, stdout_io: STDOUT, stderr_io: STDERR, ignore_exitcode: false, path: Dir.pwd, workdir: Dir.pwd) click to toggle source
# File lib/covalence/core/cli_wrappers/popen_wrapper.rb, line 67
def spawn_subprocess(env, run_cmd,
                 stdin_io: STDIN,
                 stdout_io: STDOUT,
                 stderr_io: STDERR,
                 ignore_exitcode: false,
                 path: Dir.pwd,
                 workdir: Dir.pwd)
  logger.info "path: #{path} workdir: #{workdir} run_cmd: #{run_cmd}"
  ## TODO one thing we can try is to use
  # Prctl.call(Prctl::PR_SET_PDEATHSIG, Signal.list['TERM'], 0, 0, 0)
  # so when the parent dies, child will know to terminate itself.
  Signal.trap("INT") { logger.info "Trapped Ctrl-c. Disable parent process from exiting, orphaning the child fork below which may or may not work" }
  wait_thread = nil
  Open3.popen3(env, *run_cmd, :chdir=>workdir) do |stdin, stdout, stderr, wait_thr|
    mappings = { stdin_io => stdin, stdout => stdout_io, stderr => stderr_io }
    wait_thread = wait_thr

    Signal.trap("INT") {
      Process.kill("INT", wait_thr.pid)
      Process.wait(wait_thr.pid, Process::WNOHANG)
      exit(wait_thr.value.exitstatus)
    } # let SIGINT drop into the child process

    handle_io_streams(mappings, stdin_io)
  end

  Signal.trap("INT") { exit } #Restore parent SIGINT

  return 0 if ignore_exitcode
  exit(wait_thread.value.exitstatus) unless wait_thread.value.success?
  return wait_thread.value.exitstatus
end