Ruby on Linux Pty Goes Away Without Eof, Raises Errno::Eio

Ruby on Linux PTY goes away without EOF, raises Errno::EIO

So I had to go as far as reading the C source for the PTY library to get really satisfied with what is going on here.

The Ruby PTY doc doesn't really say what the comments in the source code say.

My solution was to put together a wrapper method and to call that from my script where needed. I've also boxed into the method waiting on the process to for sure exit and the accessing of the exit status from $?:

# file: lib/safe_pty.rb

require 'pty'
module SafePty
def self.spawn command, &block

PTY.spawn(command) do |r,w,p|
begin
yield r,w,p
rescue Errno::EIO
ensure
Process.wait p
end
end

$?.exitstatus
end
end

This is used basically the same as PTY.spawn:

require 'safe_pty'
exit_status = SafePty.spawn(command) do |r,w,pid|
until r.eof? do
logger.debug r.readline
end
end

#test exit_status for zeroness

I was more than a little frustrated to find out that this is a valid response, as it was completely undocumented on ruby-doc.

Reading from a pty

It looks like this happens simply when the last slave file descriptor is closed. Considering write(1) and wall(1) will have the only file descriptor to the slave, you get EIO as soon as those finish writing.

The easiest way to keep this from happening is by keeping a file descriptor around. Right after your ptsname call, do an open(slave_name, O_RDRW).

(Curiously, you already have a slavefd variable, and the code to clean it up. Are you testing us? :p)

Perl Net::SSH2 in pty mode and EOF

Can I assume that send_eof() is not required if pty is enabled?

From docs:

send_eof

Sends an EOF to the remote side.

After an EOF has been sent, no more data may be sent to the remote
process STDIN channel.

Note that if a PTY was requested for the channel, the EOF may be ignored by the remote server.

Continuously read from STDOUT of external process in Ruby

I've had some success in solving this problem of mine. Here are the details, with some explanations, in case anyone having a similar problem finds this page. But if you don't care for details, here's the short answer:

Use PTY.spawn in the following manner (with your own command of course):

require 'pty'
cmd = "blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1"
begin
PTY.spawn( cmd ) do |stdout, stdin, pid|
begin
# Do stuff with the output here. Just printing to show it works
stdout.each { |line| print line }
rescue Errno::EIO
puts "Errno:EIO error, but this probably just means " +
"that the process has finished giving output"
end
end
rescue PTY::ChildExited
puts "The child process exited!"
end

And here's the long answer, with way too many details:

The real issue seems to be that if a process doesn't explicitly flush its stdout, then anything written to stdout is buffered rather than actually sent, until the process is done, so as to minimize IO (this is apparently an implementation detail of many C libraries, made so that throughput is maximized through less frequent IO). If you can easily modify the process so that it flushes stdout regularly, then that would be your solution. In my case, it was blender, so a bit intimidating for a complete noob such as myself to modify the source.

But when you run these processes from the shell, they display stdout to the shell in real-time, and the stdout doesn't seem to be buffered. It's only buffered when called from another process I believe, but if a shell is being dealt with, the stdout is seen in real time, unbuffered.

This behavior can even be observed with a ruby process as the child process whose output must be collected in real time. Just create a script, random.rb, with the following line:

5.times { |i| sleep( 3*rand ); puts "#{i}" }

Then a ruby script to call it and return its output:

IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end

You'll see that you don't get the result in real-time as you might expect, but all at once afterwards. STDOUT is being buffered, even though if you run random.rb yourself, it isn't buffered. This can be solved by adding a STDOUT.flush statement inside the block in random.rb. But if you can't change the source, you have to work around this. You can't flush it from outside the process.

If the subprocess can print to shell in real-time, then there must be a way to capture this with Ruby in real-time as well. And there is. You have to use the PTY module, included in ruby core I believe (1.8.6 anyways). Sad thing is that it's not documented. But I found some examples of use fortunately.

First, to explain what PTY is, it stands for pseudo terminal. Basically, it allows the ruby script to present itself to the subprocess as if it's a real user who has just typed the command into a shell. So any altered behavior that occurs only when a user has started the process through a shell (such as the STDOUT not being buffered, in this case) will occur. Concealing the fact that another process has started this process allows you to collect the STDOUT in real-time, as it isn't being buffered.

To make this work with the random.rb script as the child, try the following code:

require 'pty'
begin
PTY.spawn( "ruby random.rb" ) do |stdout, stdin, pid|
begin
stdout.each { |line| print line }
rescue Errno::EIO
end
end
rescue PTY::ChildExited
puts "The child process exited!"
end

Is there an Expect equivalent gem for Ruby?

Ruby comes with the PTY module for setting up pseudoterminals to drive interactive command line applications. With it comes an expect method that allows you to interact with an application kinda like Expect. For learning how to use expect, I found "What to expect from the Ruby expect library?" helpful.

As far as gems go, maybe checkout greenletters which is supposed to improve upon PTY + expect (although I haven't tried it myself).



Related Topics



Leave a reply



Submit