Fork child process with timeout and capture output

2020-02-26 09:33发布

问题:

Say I have a function like below, how do I capture the output of the Process.spawn call? I should also be able to kill the process if it takes longer than a specified timeout.

Note that the function must also be cross-platform (Windows/Linux).

def execute_with_timeout!(command)
  begin
    pid = Process.spawn(command)     # How do I capture output of this process?
    status = Timeout::timeout(5) {
      Process.wait(pid)
    }
  rescue Timeout::Error
    Process.kill('KILL', pid)
  end
end

Thanks.

回答1:

You can use IO.pipe and tell Process.spawn to use the redirected output without the need of external gem.

Of course, only starting with Ruby 1.9.2 (and I personally recommend 1.9.3)

The following is a simple implementation used by Spinach BDD internally to capture both out and err outputs:

# stdout, stderr pipes
rout, wout = IO.pipe
rerr, werr = IO.pipe

pid = Process.spawn(command, :out => wout, :err => werr)
_, status = Process.wait2(pid)

# close write ends so we could read them
wout.close
werr.close

@stdout = rout.readlines.join("\n")
@stderr = rerr.readlines.join("\n")

# dispose the read ends of the pipes
rout.close
rerr.close

@last_exit_status = status.exitstatus

The original source is in features/support/filesystem.rb

Is highly recommended you read Ruby's own Process.spawn documentation.

Hope this helps.

PS: I left the timeout implementation as homework for you ;-)



回答2:

I followed Anselm's advice in his post on the Ruby forum here.

The function looks like this -

def execute_with_timeout!(command)
  begin
    pipe = IO.popen(command, 'r')
  rescue Exception => e
    raise "Execution of command #{command} unsuccessful"
  end

  output = ""
  begin
    status = Timeout::timeout(timeout) {
      Process.waitpid2(pipe.pid)
      output = pipe.gets(nil)
    }
  rescue Timeout::Error
    Process.kill('KILL', pipe.pid)
  end
  pipe.close
  output
end

This does the job, but I'd rather use a third-party gem that wraps this functionality. Anyone have any better ways of doing this? I have tried Terminator, it does exactly what I want but it does not seem to work on Windows.