7

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?

Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
JavierQQ23
  • 704
  • 1
  • 8
  • 20

1 Answers1

13

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
moonfly
  • 1,810
  • 11
  • 14
  • Thanks, I didn't know about this. I do know about wait_thr.kill, but what bugs me is which one is better? (Process.kill / wait_thr.kill). I tried both and they seem to do the same. – JavierQQ23 Oct 13 '14 at 01:33
  • Well, `Process.kill` kills the process that you are waiting on (the one that generates the dots). `wait_thr.kill` kills the thread that waits on that process. That thread is created using [`Process::detach`](http://www.ruby-doc.org/core-2.1.3/Process.html#method-c-detach) on the newly created process. I didn't dive deeper into implementation, that thread may kill the child process when killed itself. But to me it seems more reliable and portable to kill the process. Then the thread will detect this fact and finish itself as well. Otherwise, the process may still continue working. – moonfly Oct 13 '14 at 01:57
  • btw, what OS are you on? if killing the `wait_thr` leaves an orphan process you can check `ps ax` on Linux/Mac OS to see what's still running there. – moonfly Oct 13 '14 at 01:59
  • I'm on Mac OS, and yes that was one of my concerns but this is my first time with these and I wasn't sure how to approach. This is what I tried to do so far (https://gist.github.com/javierqs/ebb718a284ce4cd4f1c7) – JavierQQ23 Oct 13 '14 at 02:09
  • OK, my answer was only how to kill the process. If you want to detect if it's hung, one approach is to wait on wait_thr with timeout. It can go to "sleep" while waiting for the process to complete it's job. The only indication is if it's waits too long. Another approach is checking what the process outputs, and if there are N dots in a row, considering that a fail. We've overgrown the comments format, I'll post an answer shortly. – moonfly Oct 13 '14 at 02:15
  • You are right, that's another topic I guess .. Thanks for your first answer. If I do "stdout.read" in irb it also hangs (cause its filling the file with dots), if I kill it with ctrl+c and then do "stdout.read" it shows last dot. – JavierQQ23 Oct 13 '14 at 02:27
  • Thank you so much, I tried and I'll use your second approach. Thanks again – JavierQQ23 Oct 13 '14 at 02:56
  • Killing processes with `SIGKILL` is not great practice since it doesn't let the process cleanup properly. It's better to use `SIGTERM` first and if the process doesn't terminate within a given timeout, try again with `SIGKILL`. `SIGKILL` is the same as `kill -9 ` in a shell. – Per Lundberg Mar 18 '20 at 11:44