[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[FDclone-users:00994] Re: パイプ先を間違えることができない?



 しらいです。

In Message-Id <20120711174705.6585487C001@yuka.unixusers.net>
        Takashi SHIRAI <shirai@unixusers.net>writes:
>  しらいです。

>  ちょっと体調を崩して寝込んでますので、今日のところは、結論
> に至る前の中間報告という形でご了承下さい。

 結論出ました。


>  これは、Xtcsetattr() が errno=EINTR を無視して tcsetattr()
> を呼び続けているだけのことで、無限ループしていること自体がお
> かしい訳ではなさそうです。

 こちらは根本原因ではありませんが、system call が EINTR で
失敗する場合の対処として無条件のリトライは余り賢くないので、
別途修正しておきます。
 I/O 周りの system call が SIGTTIN/SIGTTOU で中断される場合、
何度リトライしたところで結果は同じなので、EINTR に対してはそ
の元凶の signal に応じてエラーにすべきでしょうね。
 termio.c 中にはこの手の system call wrapper が沢山あるので、
今回はその修正 patch は載せませんが、次回 release にて修正し
ておきます。


>  試しに該当箇所の Xtcsetattr() を素の tcsetattr() に書換え
> てみると、誰かが SIGSTOP を発行していることに気づくと思いま
> す。

 これ SIGSTOP ではなくて SIGTTIN でした。SIGTTIN を無視して
先に進ませると SIGSTOP で割込まれていただけのことで、直接の原
因は SIGTTIN でした。
 なら話は早くて、制御端末の設定が何かのタイミングでおかしく
なってしまっただけのことです。で、頻度が小さいのでログに吐か
せて trace してみると、原因が判明しました。

 二段パイプの実行時には Bourne shell の作法だと二回 fork()
と waitpid() が実行され、その都度制御端末の切替が行なわれま
す。素直に実行されると下記の順番です。
	1.前段用に fork()。制御端末を子に移管。
	2.後段用に fork()。制御端末を前段の子に移管。
	3.後段の子を waitpid() で待つ。制御端末を親に戻す。
	4.前段の子を waitpid() で待つ。制御端末を親に戻す。
 これが異常時には 2. -> 3. -> 1. -> 4. の順序で発生していま
した。そうすると 3. で親に戻した制御端末を 1. が子に移管して
しまうので、4. で SIGTTIN が発生してしまいます。
 fork() 時の移管は子の側で行なっているのですが、その完遂を
待たずに親が次の作業に移ってしまっていたために、タイミングに
よってはこういう妙な順序が発生してしまっていました。

 そこで対処法ですが、子が制御端末の移管を完了するまで親が待
つことにしました。と言っても無限には待てないので、取り敢えず
2 秒で timeout にしています。まぁ普通は 1 秒もかかりません。
 このせいで、全体として処理が遅くなるケースが出て来るかとは
思いますが、親ばかり先走って先に進めても足並みが揃わなくなる
だけですので、対処としては正しいと思います。

 因みに timeout 時には親側で子を殺すことになるんですが、そ
の良い言い訳を思いつかなかったので取り敢えず SIGPIPE で殺し
ています。
 fork() はパイプ以外でも使うので正しくはないのですが、入出
力関連の死因と捉えればそう遠くはないと思います。子側で sleep
して無理矢理発生させてみましたが特に支障ないようです。


 という訳で patch を作成してみたのでお試し下さい。

---- Cut Here ----
diff -ur ../old/FD-3.00l/system.c ./system.c
--- ../old/FD-3.00l/system.c	Sat Jun 30 00:00:00 2012
+++ ./system.c	Thu Jul 12 23:36:48 2012
@@ -146,6 +146,7 @@
 #define	PS4STR			"+ "
 #define	UNLIMITED		"unlimited"
 #define	MAXTMPNAMLEN		8
+#define	WAITFORK		2000		/* msec */
 #define	getconstvar(s)		(getshellvar(s, strsize(s)))
 #define	constequal(s, c, l)	((l) == strsize(c) && !strnenvcmp(s, c, l))
 
@@ -527,6 +528,9 @@
 #endif
 #if	!MSDOS
 static VOID NEAR setstopsig __P_((int));
+# ifndef	NOJOB
+static int NEAR waittermio __P_((p_id_t, int));
+# endif
 static p_id_t NEAR makechild __P_((int, p_id_t, int));
 #endif	/* !MSDOS */
 static VOID NEAR safermtmpfile __P_((CONST char *));
@@ -2592,6 +2592,33 @@
 	}
 }
 
+# ifndef	NOJOB
+static int NEAR waittermio(pgrp, job)
+p_id_t pgrp;
+int job;
+{
+	p_id_t tmp;
+	int n;
+
+	if (ttypgrp < (p_id_t)0 || pgrp < (p_id_t)0) return(0);
+	if (!job) {
+		ttypgrp = pgrp;
+		return(0);
+	}
+
+	for (n = 0; n < WAITFORK; n++) {
+		if (gettcpgrp(ttyio, &tmp) < 0) break;
+		if (tmp == pgrp) {
+			ttypgrp = pgrp;
+			return(0);
+		}
+		usleep(1000L);
+	}
+
+	return(-1);
+}
+# endif	/* !NOJOB */
+
 /*ARGSUSED*/
 static p_id_t NEAR makechild(tty, ppid, stop)
 int tty;
@@ -2639,7 +2666,13 @@
 		childpgrp = (ppid >= (p_id_t)0) ? ppid : (pid) ? pid : mypid;
 	if (pid) {
 		if (jobok) VOID_C setpgroup(pid, childpgrp);
-		if (tty && ttypgrp >= (p_id_t)0) ttypgrp = childpgrp;
+		if (tty && waittermio(childpgrp, jobok) < 0) {
+			VOID_C kill(pid, SIGPIPE);
+			while (!waitjob(pid, NULL, WUNTRACED))
+				if (interrupted) break;
+
+			return((p_id_t)-1);
+		}
 #  if	defined (DEP_PTY) && defined (CYGWIN)
 		if (parentfd >= 0) addmychild(pid);
 #  endif
---- Cut Here ----

                                               しらい たかし