[インデックス 16147] ファイルの概要
このコミットは、Go言語のnetパッケージにおけるruntime.PollDescのリークの可能性を修正するものです。具体的には、ネットワーク接続(connect)またはリッスン(listen)の処理が失敗した場合に、runtime.PollDescが適切に解放されずにリソースリークが発生する問題を解決します。
コミット
commit e13341edd1f322e4a032c4e98a24db8cde45eb8d
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Apr 9 12:41:58 2013 +0900
net: fix possible runtime.PollDesc leak when connect or listen fails
Makes it possible to return the spent runtime.PollDesc to
runtime.pollcache descriptor pool when netFD.connect or
syscall.Listen fails.
Fixes #5219.
R=dvyukov, dave, bradfitz, adg
CC=golang-dev
https://golang.org/cl/8318044
---
src/pkg/net/fd_unix.go | 10 +++++++---\
src/pkg/net/sock_posix.go | 2 +-\
src/pkg/net/tcpsock_posix.go | 2 +-\
src/pkg/net/unixsock_posix.go | 2 +-\
4 files changed, 10 insertions(+), 6 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e13341edd1f322e4a032c4e98a24db8cde45eb8d
元コミット内容
net: fix possible runtime.PollDesc leak when connect or listen fails
このコミットは、netFD.connectまたはsyscall.Listenが失敗した場合に、使用されたruntime.PollDescをruntime.pollcacheディスクリプタプールに返すことを可能にします。これにより、Issue #5219で報告された問題が修正されます。
変更の背景
Go言語のネットワーク処理では、I/O操作の効率化のためにruntime.PollDescという内部構造体が使用されます。これは、ファイルディスクリプタ(ソケットなど)の状態変化を監視し、I/Oの準備ができた際にゴルーチンをスケジューリングするためのメカニズムです。runtime.PollDescは、runtime.pollcacheというプールから取得され、使用後にプールに返却されることで再利用されます。
しかし、ネットワーク接続(netFD.connect)やリッスン(syscall.Listen)の初期化処理中にエラーが発生した場合、runtime.PollDescが適切に解放されず、プールに返却されない可能性がありました。これにより、runtime.PollDescがリークし、システムのリソースが枯渇したり、パフォーマンスが低下したりする問題が発生していました。
このコミットは、このリソースリークの可能性を排除し、エラー発生時にもruntime.PollDescが確実に解放されるようにすることで、システムの安定性と効率性を向上させることを目的としています。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するためにカーネルが割り当てる整数値です。プログラムはファイルディスクリプタを通じてこれらのリソースにアクセスします。
runtime.PollDesc
Go言語のランタイム内部で使用される構造体で、ネットワークI/Oなどの非同期I/O操作を効率的に処理するためのポーリングメカニズムの一部です。各ファイルディスクリプタ(ソケット)に関連付けられ、そのディスクリプタに対する読み書きの準備ができたかどうかを監視します。これにより、GoのネットワーキングはノンブロッキングI/Oとイベント駆動型モデルを組み合わせ、多数の同時接続を効率的に処理できます。
runtime.pollcache
runtime.PollDescオブジェクトを再利用するためのキャッシュプールです。PollDescの生成と破棄はコストがかかるため、使用済みのPollDescをプールに戻して再利用することで、パフォーマンスを向上させます。
netFD
Go言語のnetパッケージにおける、ネットワークファイルディスクリプタを抽象化した内部構造体です。ソケットのファイルディスクリプタ(sysfd)や、それに関連するruntime.PollDesc(pd)などの情報を含みます。
closesocket
ソケットのファイルディスクリプタを閉じるための関数です。これにより、OSレベルでソケットリソースが解放されます。
fd.Close()
netFD構造体に関連付けられたリソースを解放するためのメソッドです。これには、runtime.PollDescの解放や、基となるソケットのクローズが含まれます。
Issue #5219
このコミットが修正するGoのバグトラッカー上の課題番号です。具体的な内容はコミットメッセージからは読み取れませんが、runtime.PollDescのリークに関する問題であったことが示唆されます。Web検索の結果からは、直接的なIssue #5219の詳細は見つかりませんでしたが、runtime.PollDescに関連するゴルーチンリークの問題(例: Issue #24455)はGoのネットワークスタックで過去に報告されており、リソース管理の重要性を示しています。
技術的詳細
このコミットの主要な変更点は、エラーパスにおけるリソース解放のロジックを改善することです。
以前の実装では、netFD.connectやsyscall.Listenが失敗した場合、単にclosesocket(s)やclosesocket(fd.sysfd)を呼び出してOSレベルのソケットディスクリプタを閉じていました。しかし、これだけではnetFD構造体に関連付けられたruntime.PollDescが適切に解放されず、runtime.pollcacheに返却されない可能性がありました。
修正後は、エラー発生時にfd.Close()メソッドを呼び出すように変更されています。fd.Close()は、netFDが保持するruntime.PollDescをfd.pd.Close()によって解放し、さらに基となるソケットディスクリプタもfd.sysfile.Close()またはclosesocket(fd.sysfd)によって閉じます。これにより、runtime.PollDescが確実にプールに返却され、リソースリークが防止されます。
特にsrc/pkg/net/fd_unix.goのdecref関数における変更は重要です。
変更前:
if fd.closing && fd.sysref == 0 && fd.sysfile != nil {
// Poller may want to unregister fd in readiness notification mechanism,
// so this must be executed before sysfile.Close().
fd.pd.Close()
fd.sysfile.Close()
fd.sysfile = nil
変更後:
if fd.closing && fd.sysref == 0 { // fd.sysfile != nil の条件が削除
// Poller may want to unregister fd in readiness notification mechanism,
// so this must be executed before sysfile.Close().
fd.pd.Close()
if fd.sysfile != nil { // sysfileが存在する場合のみClose
fd.sysfile.Close()
fd.sysfile = nil
} else { // sysfileが存在しない場合はsysfdを直接Close
closesocket(fd.sysfd)
}
fd.sysfd = -1
この変更により、fd.sysfileがnilの場合(例えば、ソケットがまだファイルとしてラップされていない初期段階でエラーが発生した場合など)でも、fd.pd.Close()が確実に呼び出され、runtime.PollDescが解放されるようになりました。また、fd.sysfileが存在しない場合はclosesocket(fd.sysfd)を直接呼び出すことで、ソケットディスクリプタも確実に閉じられます。
コアとなるコードの変更箇所
src/pkg/net/fd_unix.go
netFD構造体のdecrefメソッドが変更されました。このメソッドは、ファイルディスクリプタの参照カウントがゼロになり、クローズ処理が進行中の場合にリソースを解放する役割を担います。
if fd.closing && fd.sysref == 0 && fd.sysfile != nilの条件から&& fd.sysfile != nilが削除されました。fd.sysfile.Close()の呼び出しがif fd.sysfile != nilブロック内に移動しました。fd.sysfileがnilの場合にclosesocket(fd.sysfd)を呼び出すelseブロックが追加されました。
これにより、fd.sysfileがまだ設定されていない状態(例えば、ソケットが作成された直後で、まだos.Fileに変換されていない場合)でも、fd.pd.Close()が確実に呼び出され、runtime.PollDescが解放されるようになりました。
src/pkg/net/sock_posix.go
socket関数内のfd.connect(ursa)が失敗した場合の処理が変更されました。
- 変更前:
closesocket(s) - 変更後:
fd.Close()
src/pkg/net/tcpsock_posix.go
ListenTCP関数内のsyscall.Listenが失敗した場合の処理が変更されました。
- 変更前:
closesocket(fd.sysfd) - 変更後:
fd.Close()
src/pkg/net/unixsock_posix.go
ListenUnix関数内のsyscall.Listenが失敗した場合の処理が変更されました。
- 変更前:
closesocket(fd.sysfd) - 変更後:
fd.Close()
コアとなるコードの解説
このコミットの核心は、ネットワーク操作の失敗パスにおいて、closesocketのような低レベルのソケットクローズ関数を直接呼び出すのではなく、netFD構造体の高レベルなClose()メソッドを使用するように統一した点にあります。
netFD.Close()メソッドは、単にOSレベルのソケットを閉じるだけでなく、そのnetFDに関連付けられたruntime.PollDescを適切に解放し、runtime.pollcacheに返却する責任を負っています。これにより、connectやlistenが失敗した場合でも、runtime.PollDescがリークすることなく、リソースが確実にクリーンアップされるようになります。
特にfd_unix.goのdecref関数における変更は、netFDのライフサイクル管理の堅牢性を高めています。fd.sysfileがnilである可能性を考慮し、runtime.PollDescの解放(fd.pd.Close())を常に実行するようにすることで、より広範なエラーシナリオでのリークを防ぎます。これは、ソケットが完全に初期化される前にエラーが発生した場合など、fd.sysfileがまだ有効なos.Fileオブジェクトを指していない状況に対応するためです。
これらの変更により、Goのネットワークスタックは、エラー発生時においてもより堅牢なリソース管理を実現し、長期稼働するアプリケーションにおけるリソースリークのリスクを低減しています。
関連リンク
- Go言語の
netパッケージ: https://pkg.go.dev/net - Go言語の
syscallパッケージ: https://pkg.go.dev/syscall - Go言語のIssueトラッカー: https://github.com/golang/go/issues
参考にした情報源リンク
- Go言語のソースコード (特に
src/runtime/netpoll.goやsrc/internal/poll): https://github.com/golang/go - Go言語の
runtime.PollDescに関する議論やドキュメント (Goの内部実装に関する情報は公式ドキュメントには少ないため、ソースコードや関連するIssue、設計ドキュメントが主な情報源となります) - Goのゴルーチンリークに関する一般的な情報: