2010年8月11日8:05 NARUSE, Yui <naruse / airemix.jp>:
>
> あと、ps で wchan を見てみると、以下のようになりますね
> % pgrep a.out|xargs procstat
>  PID  PPID  PGID   SID  TSID THR LOGIN    WCHAN     EMUL          COMM
> 35305 35304 35304 47112 47112   1 naruse   ttyout    FreeBSD ELF64 a.out
> 35304 47112 35304 47112 47112   1 naruse   wait      FreeBSD ELF64 a.out

これは良い情報です。
ttyout ってことはなにか出力を待っているんですね。

子プロセスも取り除けました。

freebsd8% cat tst.c
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
  int m, s;
  char *slavedev;

  if ((m = posix_openpt(O_RDWR|O_NOCTTY)) == -1) {
perror("posix_openpt"); exit(1); }
  if (grantpt(m) == -1) { perror("grantpt"); exit(1); }
  if (unlockpt(m) == -1) { perror("unlockpt"); exit(1); }
  if ((slavedev = ptsname(m)) == NULL) { perror("ptsname"); exit(1); }
  if ((s = open(slavedev, O_RDWR|O_NOCTTY, 0)) == -1) {
perror("open"); exit(1); }

  if (write(m, "a", 1) == -1) { perror("write"); exit(1); }

  fprintf(stderr, "before close(s)\n");
  if (close(s) == -1) { perror("close"); exit(1); }
  fprintf(stderr, "after close(s)\n");

  return 0;
}
freebsd8% gcc -Wall tst.c
freebsd8% ./a.out
before close(s)
(ここでハング)

どうやら、close がブロックしているようですね。
exit も内部的には close 相当のことをするでしょうから、
そこでブロックしているのでしょう。

以下のようにしてもハングします。

% ./ruby -rpty -e '
m, s = PTY.open
m.write "a"
s.close
'

なんで出力があるかというと、おそらく tty のエコーだろうということで、
エコーを抑制するとハングしません。

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <termios.h>

int main(int argc, char *argv[])
{
  int m, s;
  char *slavedev;
  struct termios t;

  if ((m = posix_openpt(O_RDWR|O_NOCTTY)) == -1) {
perror("posix_openpt"); exit(1); }
  if (grantpt(m) == -1) { perror("grantpt"); exit(1); }
  if (unlockpt(m) == -1) { perror("unlockpt"); exit(1); }
  if ((slavedev = ptsname(m)) == NULL) { perror("ptsname"); exit(1); }
  if ((s = open(slavedev, O_RDWR|O_NOCTTY, 0)) == -1) {
perror("open(slavedev)"); exit(1); }

  if (tcgetattr(s, &t) == -1) { perror("tcgetattr"); }
  t.c_lflag &= ~(tcflag_t)(ECHO|ECHOE|ECHOK|ECHONL);
  if (tcsetattr(s, TCSANOW, &t) == -1) { perror("tcsetattr"); }

  if (write(m, "a", 1) == -1) { perror("write"); exit(1); }

  if (close(s) == -1) { perror("close"); exit(1); }

  return 0;
}

ruby ならこうです。

% ./ruby -rio/console -rpty -e '
m, s = PTY.open
s.echo = false
m.write "a"
s.close
'

では、test_script_from_stdin でも、というと、そこが微妙です。
PTY.spawn は slave tty を教えてくれないので、
slave tty に tcsetattr ができません。
FreeBSD だと以下のように master 側に tcsetattr を発行しても動くんですが、
これはポータブルではありません。[ruby-list:28382]

% svn diff --diff-cmd diff -x -u test/ruby/test_rubyoptions.rb
Index: test/ruby/test_rubyoptions.rb
===================================================================
--- test/ruby/test_rubyoptions.rb	(revision 28906)
+++ test/ruby/test_rubyoptions.rb	(working copy)
@@ -436,6 +436,7 @@
     result = nil
     s, w = IO.pipe
     PTY.spawn(EnvUtil.rubybin, out: w) do |r, m|
+      m.echo = false
       w.close
       m.print("\C-d")
       assert_nothing_raised('[ruby-dev:37798]') do
@@ -446,6 +447,7 @@
     assert_equal("", result, '[ruby-dev:37798]')
     s, w = IO.pipe
     PTY.spawn(EnvUtil.rubybin, out: w) do |r, m|
+      m.echo = false
       w.close
       m.print("$stdin.read; p $stdin.gets\n\C-d")
       m.print("abc\n\C-d")

このテストでは制御端末を変える必要はないと思うので、
PTY.open を使って書き直すのがいいかなぁ。
-- 
[田中 哲][たなか あきら][Tanaka Akira]