Goのos.ReadはO_NONBLOCK+EAGAINでもブロックする

Go言語で/dev/kmsgのようなデバイスファイルをO_NONBLOCKフラグ付きで開いてos.File.Readしても、C言語のようにEAGAINを受け取ってすぐ制御が戻ってくることを期待するとハングする。

原因

Goのos.Read(Linux実装)は、内部のread(2)システムコールがEAGAINを返した場合、それをアプリケーション側にエラーとして返さない。代わりにfd.pd.pollable()trueのときfd.pd.waitReadが呼ばれ、readyになるまで(=データが来るまで)内部で待機し続ける。

GoランタイムはnetpollerというepollベースのI/Oポーリング機構を使い、ノンブロッキングI/Oをアプリケーションから見て「あたかもブロッキングI/Oであるかのように」隠蔽する設計になっている。そのため、呼び出し元はEAGAINを観測できず、O_NONBLOCKフラグを立てただけでは真にノンブロッキングな挙動にならない。

検証方法

元記事の著者は以下の手順で原因を突き止めている。

  1. straceで追跡し、①readO_NONBLOCK付きで実行されEAGAINを返している、②その直後にepoll_pwaitがtimeout=-1(無限待ち)で呼ばれていることを確認。
  2. gdbcatch syscall機能で、そのepoll_pwait呼び出しがruntime.schedule → runtime.findRunnableというGoランタイムのスケジューラ内部から発生していることを突き止めた。

回避策

  1. SetReadDeadlineでタイムアウトを設定するos.ErrDeadlineExceededを検知して抜ける。
  2. SyscallConnインターフェースを使う — 低レベルのread(2)を直接呼び、netpollerの自動待機を経由せずEAGAINを自分でハンドリングする。

出典

#golang #linux

作成日時: 2026-08-15 09:11 / 更新日時: 2026-08-15 09:14