Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.PollDescruntime.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.PollDescpd)などの情報を含みます。

closesocket

ソケットのファイルディスクリプタを閉じるための関数です。これにより、OSレベルでソケットリソースが解放されます。

fd.Close()

netFD構造体に関連付けられたリソースを解放するためのメソッドです。これには、runtime.PollDescの解放や、基となるソケットのクローズが含まれます。

Issue #5219

このコミットが修正するGoのバグトラッカー上の課題番号です。具体的な内容はコミットメッセージからは読み取れませんが、runtime.PollDescのリークに関する問題であったことが示唆されます。Web検索の結果からは、直接的なIssue #5219の詳細は見つかりませんでしたが、runtime.PollDescに関連するゴルーチンリークの問題(例: Issue #24455)はGoのネットワークスタックで過去に報告されており、リソース管理の重要性を示しています。

技術的詳細

このコミットの主要な変更点は、エラーパスにおけるリソース解放のロジックを改善することです。

以前の実装では、netFD.connectsyscall.Listenが失敗した場合、単にclosesocket(s)closesocket(fd.sysfd)を呼び出してOSレベルのソケットディスクリプタを閉じていました。しかし、これだけではnetFD構造体に関連付けられたruntime.PollDescが適切に解放されず、runtime.pollcacheに返却されない可能性がありました。

修正後は、エラー発生時にfd.Close()メソッドを呼び出すように変更されています。fd.Close()は、netFDが保持するruntime.PollDescfd.pd.Close()によって解放し、さらに基となるソケットディスクリプタもfd.sysfile.Close()またはclosesocket(fd.sysfd)によって閉じます。これにより、runtime.PollDescが確実にプールに返却され、リソースリークが防止されます。

特にsrc/pkg/net/fd_unix.godecref関数における変更は重要です。 変更前:

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.sysfilenilの場合(例えば、ソケットがまだファイルとしてラップされていない初期段階でエラーが発生した場合など)でも、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.sysfilenilの場合に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に返却する責任を負っています。これにより、connectlistenが失敗した場合でも、runtime.PollDescがリークすることなく、リソースが確実にクリーンアップされるようになります。

特にfd_unix.godecref関数における変更は、netFDのライフサイクル管理の堅牢性を高めています。fd.sysfilenilである可能性を考慮し、runtime.PollDescの解放(fd.pd.Close())を常に実行するようにすることで、より広範なエラーシナリオでのリークを防ぎます。これは、ソケットが完全に初期化される前にエラーが発生した場合など、fd.sysfileがまだ有効なos.Fileオブジェクトを指していない状況に対応するためです。

これらの変更により、Goのネットワークスタックは、エラー発生時においてもより堅牢なリソース管理を実現し、長期稼働するアプリケーションにおけるリソースリークのリスクを低減しています。

関連リンク

参考にした情報源リンク