Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kill a process called using open3 in ruby

Tags:

ruby

popen3

I'm using a command line program, it works as mentioned below:

$ ROUTE_TO_FOLDER/app < "long text"

If "long text" is written using the parameters "app" needs, then it will fill a text file with results. If not, it will fill the text file with dots continuously (I can't handle or modify the code of "app" in order to avoid this).

In a ruby script there's a line like this:

text = "long text that will be used by app"
output = system("ROUTE_TO_FOLDER/app < #{text}")

Now, if text is well written, there won't be problems and I will get an output file as mentioned before. The problem comes when text is not well written. What happens next is that my ruby script hangs and I'm not sure how to kill it.

I've found Open3 and I've used the method like this:

irb> cmd = "ROUTE_TO_FOLDER/app < #{text}"
irb> stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
=> [#<IO:fd 10>, #<IO:fd 11>, #<IO:fd 13>, #<Thread:0x007f3a1a6f8820 run>]

When I do:

irb> wait_thr.value

it also hangs, and :

irb> wait_thr.status
=> "sleep"

How can I avoid these problems? Is it not recognizing that "app" has failed?

like image 947
JavierQQ23 Avatar asked Oct 09 '14 00:10

JavierQQ23


1 Answers

wait_thr.pid provides you the pid of the started process. Just do

Process.kill("KILL",wait_thr.pid)

when you need to kill it.

You can combine it with detecting if the process is hung (continuously outputs dots) in one of the two ways.

1) Set a timeout for waiting for the process:

get '/process' do
  text = "long text that will be used by app"
  cmd = "ROUTE_TO_FOLDER/app < #{text}"
  Open3.popen3(cmd) do |i,o,e,w|
    begin
      Timeout.timeout(10) do # timeout set to 10 sec, change if needed
        # process output of the process. it will produce EOF when done.
        until o.eof? do
          # o.read_nonblock(N) ...
        end
      end
    rescue Timeout::Error
      # here you know that the process took longer than 10 seconds
      Process.kill("KILL", w.pid)
      # do whatever other error processing you need
    end
  end
end

2) Check the process output. (The code below is simplified - you probably don't want to read the output of your process into a single String buf first and then process, but I guess you get the idea).

get '/process' do
  text = "long text that will be used by app"
  cmd = "ROUTE_TO_FOLDER/app < #{text}"
  Open3.popen3(cmd) do |i,o,e,w|
    # process output of the process. it will produce EOF when done. 
    # If you get 16 dots in a row - the process is in the continuous loop
    # (you may want to deal with stderr instead - depending on where these dots are sent to)
    buf = ""
    error = false
    until o.eof? do
      buf << o.read_nonblock(16)
      if buf.size>=16 && buf[-16..-1] == '.'*16
        # ok, the process is hung
        Process.kill("KILL", w.pid)
        error = true
        # you should also get o.eof? the next time you check (or after flushing the pipe buffer),
        # so you will get out of the until o.eof? loop
      end
    end
    if error
      # do whatever error processing you need
    else
      # process buf, it contains all the output
    end
  end
end
like image 72
moonfly Avatar answered Nov 15 '22 07:11

moonfly