Forum: Ruby Can not catch PTY::ChildExited exception

5d46c887d941733ee503466ff281bc29?d=identicon&s=25 Denis Berezhnoy (denisb)
on 2009-09-09 08:14
Hi guys!

I have problem that I can not catch PTY::ChildExited exception. Here is
the code:

require 'pty'

cmd = "/home3/cman/work/product/integrator/test/syb2/OCS-15_0/bin/isql
-Uuser2 -Puser2 -Scman2"

begin

    pipe_read, pipe_write, pid = PTY.spawn(cmd)

rescue PTY::ChildExited => msg
    puts "Caught child exited exception!"
rescue => msg
    puts "Caught exception!"
end

sleep 10

printf "TEST END\n"

When I run it I have the following:

pty_test.rb:7: pty - exited: 1278 (PTY::ChildExited)


I am wondering why exception is not caught?  What is more when I remove
sleep operator then  there is no exception  at all.  Script works and
prints TEST END.

My environment:

ruby 1.8.7 (2008-06-20 patchlevel 22) [x86_64-linux]
Linux 2.6.24-23-server #1 SMP Mon Jan 26 01:36:05 UTC 2009 x86_64
GNU/Linux

What I am doing wrong?

Best regards.
Denis
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 Yukihiro Matsumoto (Guest)
on 2009-09-10 15:41
(Received via mailing list)
Hi,

In message "Re: Can not catch PTY::ChildExited exception"
    on Wed, 9 Sep 2009 15:14:09 +0900, Denis Berezhnoy
<denis.berezhnoy@gmail.com> writes:

|I have problem that I can not catch PTY::ChildExited exception. Here is
|the code:

The exception was raised when the child process terminated
(asynchronously), so you need to wrap not only spawn but everything.

              matz.
5d46c887d941733ee503466ff281bc29?d=identicon&s=25 Denis Berezhnoy (denisb)
on 2009-09-11 02:56
Yukihiro Matsumoto wrote:
> Hi,
>
> In message "Re: Can not catch PTY::ChildExited exception"
>     on Wed, 9 Sep 2009 15:14:09 +0900, Denis Berezhnoy
> <denis.berezhnoy@gmail.com> writes:
>
> |I have problem that I can not catch PTY::ChildExited exception. Here is
> |the code:
>
> The exception was raised when the child process terminated
> (asynchronously), so you need to wrap not only spawn but everything.
>
>               matz.

Hello Matsumoto-san,

Thank you very much for clarifications! I tried to wrap around whole
code with begin rescue block and I can catch ChildExited exception.  So
now I have code like this:

require 'pty'

cmd = "/home3/cman/work/product/integrator/test/syb2/OCS-15_0/bin/isql
-Uuser2 -Puser2 -Scman2"

begin

pipe_read, pipe_write, pid = PTY.spawn(cmd)

sleep 10

printf "TEST END\n"

rescue PTY::ChildExited => msg
    puts "Caught child exited exception!"
rescue => msg
    puts "Caught exception!"
rescue => e
    puts e.msg
    puts e.type
end


But now the problem is that ChildExited exception can occur at any
moment after spawn and interrupt program in random place. In my test
above I never get TEST END printed.

How can I handle this in right way?

Best regards,
Denis
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 Yukihiro Matsumoto (Guest)
on 2009-09-11 04:21
(Received via mailing list)
Hi,

In message "Re: Can not catch PTY::ChildExited exception"
    on Fri, 11 Sep 2009 09:56:15 +0900, Denis Berezhnoy
<denis.berezhnoy@gmail.com> writes:

|But now the problem is that ChildExited exception can occur at any
|moment after spawn and interrupt program in random place. In my test
|above I never get TEST END printed.
|
|How can I handle this in right way?

Define the "right way" first.

I admit asynchronous exception is a bad design choice.  pty bundled
with 1.9 has changed to use polling via pty.check method.

              matz.
Dddc63b63be3f9abeca7b0bebb383b80?d=identicon&s=25 Ehsanul Hoque (Guest)
on 2009-09-11 04:36
(Received via mailing list)
Well, I'm sure I have nowhere as good an idea of PTY.spawn() as
Matsumoto does, but let me try to explain what your code is doing, and
how to get your desired result.

First, you might want to consider using other methods for what you're
doing. You probably don't really need a pseudo terminal, which is what
PTY gives you, and you can use more well documented methods. These two
links give a good overview of some of the other ways you can start
subprocesses in ruby:
http://devver.net/blog/2009/06/a-dozen-or-so-ways-...
http://devver.net/blog/2009/07/a-dozen-or-so-ways-...

If you still think you really need to use a pseudo terminal, then read
on.

PTY.spawn() doesn't block your ruby program like a call to system()
would. It simply starts the process given the command, and lets you
continue. In your first email, you mentioned that when you took out the
"sleep 10" line in your code, no exception took place at all. The reason
for this is that your script ended before the child did, since the ten
seconds of sleep were taken out. And so ruby did not have a chance to
raise an exception. Similarly, in your latest code example, "TEST END"
was never printed because the
begin block had an exception before you even reached the 'printf "TEST
END\n"' line. The exception occurred while
it was in "sleep 10".  I hope that's clear, it messed me up too in the
past.

To get what you want, you might want to use the block form for
PTY.spawn() instead. I just find it cleaner, and using the limited scope
for the variables pipe_read etc makes sense. The variables are gone when
the pseudo terminal is gone.

What I've done is put a loop inside the PTY.spawn() block, which can be
used to handle the output from the subprocess, or feed it input. If you
don't need to do that, you probably don't need to use PTY anyways, but
you can be the judge of that. Now, this loop stops when the subprocess
exits, and then the rest of the ruby script continues.

It seems like you also want to run some code in parallel to the
subprocess, but not in the loop I mentioned. For this case, the code
below also has a thread. Only when the child exits does the thread stop,
and "t.join" finally allows the code to continue. Before "t.join" and
after the thread block though, you can run code in parallel to the
subprocess. And then, "Test End" is printed after the subprocess has
exited. Here's the code:

require 'pty'

cmd = "/home3/cman/work/product/integrator/test/syb2/OCS-15_0/bin/isql
-Uuser2 -Puser2 -Scman2"

t = Thread.new do
  begin
    PTY.spawn(cmd) do |pipe_read, pipe_write, pid|
      loop do
        # Code here can handle output given by your command, and feed it
input, etc
        # But the infinite loop will be broken by the exception caused
when the child process exits, and your script continues
        # Or you could just terminate this loop in advance, but
remember, you might reach the end of your ruby script
        # before the child exits, in case that's a problem.
      end
    end
  rescue PTY::ChildExited => msg
    puts "The child has exited"
  rescue => msg
    puts "A different exception:\n" + msg
  end
end

puts "Doing stuff in parallel to the subprocess here"
t.join
puts "Test End"


I hope that was useful/clear. It was certainly too long.

- Ehsan
5d46c887d941733ee503466ff281bc29?d=identicon&s=25 Denis Berezhnoy (denisb)
on 2009-09-11 04:53
Yukihiro Matsumoto wrote:
> Hi,
>
> In message "Re: Can not catch PTY::ChildExited exception"
>     on Fri, 11 Sep 2009 09:56:15 +0900, Denis Berezhnoy
> <denis.berezhnoy@gmail.com> writes:
>
> |But now the problem is that ChildExited exception can occur at any
> |moment after spawn and interrupt program in random place. In my test
> |above I never get TEST END printed.
> |
> |How can I handle this in right way?
>
> Define the "right way" first.
>
> I admit asynchronous exception is a bad design choice.  pty bundled
> with 1.9 has changed to use polling via pty.check method.
>
>               matz.

Hi,

Saying about "right way" I meant that may be there is a standard Ruby
mechanism  to handle such async exceptions besides begin rescue
operators. I am newbie in Ruby so I am not sure about its capabilities.

But you answered my question. This is a flaw in design of Ruby PTY
component and we can not avoid interrupting code by child exited
exception.

Anyway thanks for clarification. I will look how I can patch PTY in Ruby
to eliminate generating PTY::ChildExited exception at all.

Best regards,
Denis
5d46c887d941733ee503466ff281bc29?d=identicon&s=25 Denis Berezhnoy (denisb)
on 2009-09-11 07:55
Ehsanul Hoque wrote:
> Well, I'm sure I have nowhere as good an idea of PTY.spawn() as
> Matsumoto does, but let me try to explain what your code is doing, and
> how to get your desired result.
>
> First, you might want to consider using other methods for what you're
> doing. You probably don't really need a pseudo terminal, which is what
> PTY gives you, and you can use more well documented methods.

Hi Ehsan,

Thanks for detailed explanation. The reason why I use PTY is because I
need to interact with application that does not flush its output to
pipe.  So when I use IO.popen or IO.pipe there is no output.

PTY.spawn() solves this problem so I use it intentionally.

Best regards,
Denis
5d46c887d941733ee503466ff281bc29?d=identicon&s=25 Denis Berezhnoy (denisb)
on 2009-09-11 10:08
Denis Berezhnoy wrote:
> Ehsanul Hoque wrote:
>> Well, I'm sure I have nowhere as good an idea of PTY.spawn() as
>> Matsumoto does, but let me try to explain what your code is doing, and
>> how to get your desired result.
>>
>> First, you might want to consider using other methods for what you're
>> doing. You probably don't really need a pseudo terminal, which is what
>> PTY gives you, and you can use more well documented methods.
>
> Hi Ehsan,
>
> Thanks for detailed explanation. The reason why I use PTY is because I
> need to interact with application that does not flush its output to
> pipe.  So when I use IO.popen or IO.pipe there is no output.
>
> PTY.spawn() solves this problem so I use it intentionally.
>
> Best regards,
> Denis

Hi Ehsan,

One more question.   Can I use Process.wait to wait for termination of
the process which was run by PTY.spawn?

Best regards,
Denis
Dddc63b63be3f9abeca7b0bebb383b80?d=identicon&s=25 Ehsanul Hoque (Guest)
on 2009-09-11 11:35
(Received via mailing list)
Denis,

That's a good reason, exactly the same reason I needed to find out so
much about PTY in fact.

As for Process.wait, as long as you have the pid, it should work. I
tried it out in irb now, and it seems to work. But you will get the
child exited exception still. I hadn't thought of this in my
explanation, it's definitely a better way to wait for the process to
finish compared to my "t.join". Here's what I tried in irb:

def test
a,b,pid = PTY.spawn("sleep 5")
Process.wait(pid)
end
test
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.