Skip to content

Instantly share code, notes, and snippets.

@thinkerbot
Created October 14, 2011 03:04
Show Gist options
  • Save thinkerbot/1286152 to your computer and use it in GitHub Desktop.
Save thinkerbot/1286152 to your computer and use it in GitHub Desktop.
Sporadic missing EOF for pty shell

Demonstrates a pecuilar problem whereby it appears a shell session occasionally does not produce an EOF on the pty slave after exit. I'm not sure this is an issue with the implementation of PTY or an OS X issue. It does not appear to be a shell issue per-se as I can produce the failure with bash, zsh, ksh, csh, and tcsh.

Example failures:

thinkerbot:~/Documents/Gems/shell_test> ruby --version
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.4.0]

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/bash
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:425 pid:13569) (RuntimeError)
"exit 8\r\n$ exit 8\r\nexit\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	0m4.323s
user	0m0.839s
sys	0m1.917s

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/ksh
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:1056 pid:14626) (RuntimeError)
"exit 8\r\n$ exit 8\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	0m10.645s
user	0m1.849s
sys	0m6.187s

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/zsh
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:8027 pid:30685) (RuntimeError)
"exit 8\r\n\e[1m\e[7m%\e[m\e[1m\e[m                                                                               \r\r\e[m\e[m\e[m\e[J$ \e[Ke\bexit 8\r\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	2m15.123s
user	0m34.373s
sys	1m9.930s

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/ksh
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:168 pid:30856) (RuntimeError)
"exit 8\r\n$ exit 8\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	0m2.157s
user	0m0.283s
sys	0m0.867s

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/csh
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:554 pid:31965) (RuntimeError)
"exit 8\r\n[b13924:~/Documents/Gems/shell_test] thinkerbot% exit 8\r\r\nexit\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	0m7.299s
user	0m2.046s
sys	0m3.970s

thinkerbot:~/Documents/Gems/shell_test> time ruby pty_fail.rb 10000 /bin/tcsh
pty_fail.rb:19:in `block (2 levels) in <main>': timeout waiting for slave EOF (i:410 pid:32786) (RuntimeError)
"exit 8\r\n[b13924:~/Documents/Gems/shell_test] thinkerbot% exit 8\r\r\nexit\r\n"
	from pty_fail.rb:9:in `spawn'
	from pty_fail.rb:9:in `block in <main>'
	from pty_fail.rb:8:in `upto'
	from pty_fail.rb:8:in `<main>'

real	0m5.819s
user	0m1.524s
sys	0m2.977s
require 'pty'
#############################################################################
# $ ruby --version
# ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin10.8.0]
# $ ruby pty_no_eof_example.rb 10000
# ..pty_no_eof_example.rb:35:in `block (2 levels) in <main>': timeout waiting for slave EOF (cmd: /bin/bash i:258 pid:37196) (RuntimeError)
# from pty_no_eof_example.rb:23:in `spawn'
# from pty_no_eof_example.rb:23:in `block in <main>'
# from pty_no_eof_example.rb:21:in `upto'
# from pty_no_eof_example.rb:21:in `<main>
#############################################################################
# $ ruby --version
# ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]
# $ ruby pty_no_eof_example.rb 10000
# ....................................................................................................
#############################################################################
max = ARGV.shift || 10000
shell = ARGV.shift || '/bin/bash'
timeout = 3
1.upto(max.to_i) do |i|
exitstatus = nil
PTY.spawn(shell) do |slave, master, pid|
begin
# exit as soon as possible
unless IO.select(nil,[master],nil,timeout)
raise "timeout waiting for master (cmd: #{shell} i:#{i} pid:#{pid})"
end
master.write("exit 8\n")
# read to EOF
while true
unless IO.select([slave],nil,nil,timeout)
raise "timeout waiting for slave EOF (cmd: #{shell} i:#{i} pid:#{pid})"
end
begin
c = slave.read(1)
rescue(Errno::EIO)
c = nil
end
if c.nil?
break
end
end
# Cleanup and capture the exit status to validate the exit worked
Process.wait(pid)
exitstatus = $?.exitstatus
rescue PTY::ChildExited
# Wait can cause a ChildExited error on 1.8.6 and 1.8.7 so handle it as
# a normal exit route. 1.9.2 does not exit this way.
exitstatus = $!.status.exitstatus
rescue Exception
# Cleanup on error - note PTY::ChildExited must be accounted for again
Process.kill(9, pid)
Process.wait(pid) rescue PTY::ChildExited
raise
end
end
unless exitstatus == 8
raise "\nexpected exit status 8 but was #{exitstatus.inspect}"
end
if i % 100 == 0
$stdout.print '.'
$stdout.flush
end
end
puts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment