作ることに関わるひとを幸福にしたい スキ: 個人開発 設計 アジャイル 関数型 DDD
Friend only
This note is available to friends only.
17 days ago
Sign in to read

子プロセス用 pipe は exec 前に不要 FD を閉じる

CLI ラッパーから別プロセスへ prompt を stdin で渡す処理が、子プロセスの終了待ちで必ず timeout した。終了監視には waitpid を使っていたが、原因は監視ではなく pipe の file descriptor(FD)継承だった。

親が prompt を書き終えて stdin の書き込み側 FD を閉じても、子が同じ書き込み側 FD を継承していると EOF が届かない。pipe は書き込み側 FD が 1 つでも開いている間、読み込み側へ「これ以上データが来ない」と通知できないためだ。

親: stdin_write を閉じる
子: stdin_write をまだ保持している
  ↓
子が stdin の EOF を待ち続ける
  ↓
子が終了しない
  ↓
waitpid は正常に「未終了」と判定する

OCaml の Unix.pipe は、cloexec を省略すると歴史的理由で keep-on-exec になる。公式ドキュメントでも安全なデフォルトではないため明示指定が推奨されている。

let stdin_read, stdin_write = Unix.pipe ~cloexec:true () in
let stdout_read, stdout_write = Unix.pipe ~cloexec:true () in
let stderr_read, stderr_write = Unix.pipe ~cloexec:true () in

close-on-exec にすると、fork 後に別プログラムへ exec する時点で不要な FD が閉じられる。親が prompt を書き終えて閉じれば、子へ EOF が届いて正常終了できる。

回帰テストでは、stdin を EOF まで読む偽 CLI を使う。stdin を読まない偽 CLI では FD リークを再現できない。

cat >/dev/null
printf '%s\n' '{"summary":"Summary","items":[]}'

終了待ちが timeout したとき、waitpid の置き換えやプロセス名ポーリングを足す前に、子が終了可能な FD 状態で起動されているかを確認する。

参考: OCaml Unix API

17 days ago
Friend only
This note is available to friends only.
a month ago
Sign in to read