Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fork child process with timeout and capture output

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.

like image 862
thegreendroid Avatar asked Aug 30 '12 04:08

thegreendroid


People also ask

What is the output in the child process?

The read end of one pipe serves as standard input for the child process, and the write end of the other pipe is the standard output for the child process.

Does child process inherit stdout?

The stdin handle to the child process, if any, will be closed before waiting. This helps avoid deadlock: it ensures that the child does not block waiting for input from the parent, while the parent waits for the child to exit. By default, stdin, stdout and stderr are inherited from the parent.

How does a process know after a call to fork () Whether it is the child or the parent?

After calling fork , the program can use the fork return value to tell whether executing in the parent or child. If the return value is 0 the program executes in the new child process.

How many child processes can a process have?

A parent process may have multiple child processes, but a child process only one parent process. On the success of a fork() system call: The Process ID (PID) of the child process is returned to the parent process. 0 is returned to the child process.


2 Answers

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 ;-)

like image 112
Luis Lavena Avatar answered Oct 12 '22 13:10

Luis Lavena


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.

like image 23
thegreendroid Avatar answered Oct 12 '22 12:10

thegreendroid