[インデックス 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のゴルーチンリークに関する一般的な情報: