[インデックス 14568] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおけるconn
型のFile()
メソッドに関するドキュメントの修正です。具体的には、File()
メソッドが基盤となるos.File
をブロッキングモードに設定し、そのコピーを返すという動作について、より明確な説明を追加しています。これにより、このメソッドの挙動に関する潜在的な誤解を解消し、開発者が予期せぬブロッキング動作に遭遇するのを防ぐことを目的としています。
コミット
- コミットハッシュ:
5416e6e916b56160b9b3b4f4d25118cd739dc0dc
- 作者: Rick Arnold rickarnoldjr@gmail.com
- コミット日時: 2012年12月5日 水曜日 23:31:35 -0500
- 変更ファイル:
src/pkg/net/net.go
- 変更行数: 6行 (5行追加, 1行削除)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5416e6e916b56160b9b3b4f4d25118cd739dc0dc
元コミット内容
net: document that File reverts connection to blocking mode.
Fixes #2458.
R=mikioh.mikioh, rsc
CC=golang-dev
https://golang.org/cl/6869054
変更の背景
このコミットは、Go言語のnet
パッケージにおけるconn
型のFile()
メソッドの挙動に関する既存の問題、具体的にはIssue #2458を修正するために行われました。
net.Conn
インターフェースを実装する型(例: *net.TCPConn
)は、ネットワーク接続を表します。これらの接続は通常、非ブロッキングI/Oモードで動作するように設定されています。非ブロッキングI/Oは、ネットワーク操作が即座に完了しない場合でもプログラムの実行をブロックせず、他のタスクを並行して処理できるようにするために重要です。
しかし、File()
メソッドは、基盤となるネットワークソケットのファイルディスクリプタを*os.File
として公開します。os.File
は、Goの標準的なファイルI/O操作に使用される型であり、通常はブロッキングモードで動作することを前提としています。
問題は、net.Conn
が非ブロッキングモードで動作しているにもかかわらず、File()
メソッドを呼び出すと、返される*os.File
がブロッキングモードに設定されるという点にありました。この挙動が明確にドキュメント化されていなかったため、開発者はFile()
から取得した*os.File
が元のnet.Conn
と同じ非ブロッキングモードで動作すると誤解し、予期せぬブロッキング動作やデッドロックに遭遇する可能性がありました。
このコミットは、この重要な挙動をFile()
メソッドのドキュメントに明記することで、開発者の混乱を防ぎ、より堅牢なネットワークアプリケーションの構築を支援することを目的としています。
前提知識の解説
1. net.Conn
とネットワークソケット
net.Conn
はGo言語のnet
パッケージで定義されているインターフェースで、汎用的なネットワーク接続を表します。TCP接続 (*net.TCPConn
) やUDP接続 (*net.UDPConn
) など、様々な種類のネットワーク接続がこのインターフェースを実装します。内部的には、これらの接続はオペレーティングシステムが提供するネットワークソケット(ファイルディスクリプタによって識別される)を介して通信を行います。
2. os.File
とファイルディスクリプタ
os.File
はGo言語のos
パッケージで定義されている構造体で、ファイルやファイルに似たリソース(パイプ、ソケットなど)を表します。オペレーティングシステムは、開かれたファイルやソケットを「ファイルディスクリプタ」(整数値)で管理します。os.File
は、このファイルディスクリプタをラップし、ファイルI/O操作のためのメソッド(Read
, Write
, Close
など)を提供します。
3. ブロッキングI/Oと非ブロッキングI/O
I/O操作(読み書き)には、大きく分けてブロッキングI/Oと非ブロッキングI/Oの2つのモードがあります。
- ブロッキングI/O: I/O操作が完了するまで、呼び出し元のプログラムの実行を停止(ブロック)します。例えば、ネットワークからデータを読み込む際にデータが到着するまで待機したり、データを書き込む際にバッファが空くまで待機したりします。シンプルですが、多数の同時接続を扱うサーバーなどではパフォーマンスのボトルネックになる可能性があります。
- 非ブロッキングI/O: I/O操作が即座に完了しない場合でも、呼び出し元のプログラムの実行をブロックしません。操作が完了していない場合は、エラー(例:
EAGAIN
やEWOULDBLOCK
)を返したり、読み書き可能なバイト数を返したりします。プログラムはI/O操作の完了を待つ間に他のタスクを実行できます。非ブロッキングI/Oは、イベント駆動型プログラミングや、多数の同時接続を効率的に処理するネットワークサーバーで広く利用されます。
Goのnet
パッケージは、内部的に非ブロッキングI/OとGoルーチンのスケジューリングを組み合わせて、開発者にはブロッキングI/Oのように見えるAPIを提供しています。しかし、File()
メソッドのように基盤となるファイルディスクリプタを直接扱う場合、そのファイルディスクリプタのブロッキングモードが重要になります。
4. dup()
システムコール
dup()
(またはdup2()
)は、Unix系オペレーティングシステムで提供されるシステムコールです。既存のファイルディスクリプタの複製を作成します。複製されたファイルディスクリプタは、元のファイルディスクリプタと同じオープンファイル記述(ファイルオフセット、ファイルステータスフラグなど)を参照します。これにより、同じファイルやソケットに対して複数のファイルディスクリプタからアクセスできるようになります。
net.Conn
のFile()
メソッドがdup()
を使用するのは、元のネットワーク接続のファイルディスクリプタをそのまま返すのではなく、そのコピーを返すことで、*os.File
の操作が元のnet.Conn
の動作に直接影響を与えないようにするためです。
技術的詳細
net.Conn
インターフェースを実装する具体的な型(例: *net.TCPConn
)は、内部にnetFD
という構造体を持っています。このnetFD
が、実際のネットワークソケットのファイルディスクリプタを管理しています。
conn
型のFile()
メソッドは、このnetFD
のdup()
メソッドを呼び出します。netFD.dup()
の役割は以下の通りです。
- ファイルディスクリプタの複製:
dup()
システムコールを使用して、元のネットワークソケットのファイルディスクリプタの複製を作成します。これにより、File()
が返す*os.File
は、元のnet.Conn
とは異なるファイルディスクリプタを持つことになります。 - ブロッキングモードへの設定: 複製されたファイルディスクリプタをブロッキングモードに設定します。これは、
os.File
が通常ブロッキングI/Oを期待するためです。Goのnet
パッケージは、内部で非ブロッキングI/OとGoルーチンのスケジューリングを組み合わせていますが、os.File
として公開される際には、一般的なファイルI/Oのセマンティクスに合わせるためにブロッキングモードが選択されます。 *os.File
の作成: 新しく複製され、ブロッキングモードに設定されたファイルディスクリプタを使用して、新しい*os.File
インスタンスを作成し、それを返します。
この挙動の重要な含意は以下の通りです。
- 独立性:
File()
が返す*os.File
は、元のnet.Conn
とは異なるファイルディスクリプタを持つため、*os.File
を閉じても元のnet.Conn
は閉じられません。また、その逆も同様です。 - モードの変更:
File()
を呼び出すことで、返される*os.File
はブロッキングモードになります。これは、元のnet.Conn
が非ブロッキングモードで動作している場合でも発生します。このモードの変更は、特に注意が必要です。例えば、net.Conn
で非ブロッキング操作を行っていたコードが、File()
を呼び出した後に返された*os.File
を介して操作を続けると、予期せぬブロッキングが発生する可能性があります。 - プロパティ変更の影響: コミットメッセージの新しいドキュメントにもあるように、「返された
os.File
のファイルディスクリプタは接続のファイルディスクリプタとは異なります。この複製を使用して元のプロパティを変更しようとしても、望ましい効果が得られる場合と得られない場合があります。」これは、dup()
によって複製されたファイルディスクリプタは、元のファイルディスクリプタとは独立した存在であるため、例えばfcntl
などを使って複製されたファイルディスクリプタのプロパティを変更しても、元の接続のファイルディスクリプタのプロパティには影響しない可能性があることを示唆しています。
このコミットは、この「ブロッキングモードへの設定」という重要な副作用を明示的にドキュメントに追加することで、開発者がこのメソッドを安全かつ正しく使用できるようにしています。
コアとなるコードの変更箇所
--- a/src/pkg/net/net.go
+++ b/src/pkg/net/net.go
@@ -197,9 +197,13 @@ func (c *conn) SetWriteBuffer(bytes int) error {
return setWriteBuffer(c.fd, bytes)
}
-// File returns a copy of the underlying os.File, set to blocking mode.
+// File sets the underlying os.File to blocking mode and returns a copy.
// It is the caller's responsibility to close f when finished.
// Closing c does not affect f, and closing f does not affect c.
+//
+// The returned os.File's file descriptor is different from the connection's.
+// Attempting to change properties of the original using this duplicate
+// may or may not have the desired effect.
func (c *conn) File() (f *os.File, err error) { return c.fd.dup() }
// An Error represents a network error.
コアとなるコードの解説
変更はsrc/pkg/net/net.go
ファイルのconn
型のFile()
メソッドのコメント部分に集中しています。
変更前:
// File returns a copy of the underlying os.File, set to blocking mode.
// It is the caller's responsibility to close f when finished.
// Closing c does not affect f, and closing f does not affect c.
このコメントは、「基盤となるos.File
のコピーをブロッキングモードで返す」という重要な情報をすでに含んでいました。しかし、その後の行で「呼び出し元はf
を閉じなければならない」「c
を閉じてもf
に影響せず、f
を閉じてもc
に影響しない」と、独立性について説明していました。
変更後:
// File sets the underlying os.File to blocking mode and returns a copy.
// It is the caller's responsibility to close f when finished.
// Closing c does not affect f, and closing f does not affect c.
//
// The returned os.File's file descriptor is different from the connection's.
// Attempting to change properties of the original using this duplicate
// may or may not have the desired effect.
主な変更点は以下の2点です。
-
最初の行の修正:
- 変更前:
// File returns a copy of the underlying os.File, set to blocking mode.
- 変更後:
// File sets the underlying os.File to blocking mode and returns a copy.
この変更は、File()
メソッドが単にブロッキングモードに設定されたコピーを「返す」だけでなく、実際に「基盤となるos.File
をブロッキングモードに設定する」という能動的な動作をより正確に表現しています。これは、dup()
が新しいファイルディスクリプタを作成し、それをブロッキングモードに設定するという内部的な挙動を反映しています。
- 変更前:
-
新しい段落の追加:
// The returned os.File's file descriptor is different from the connection's. // Attempting to change properties of the original using this duplicate // may or may not have the desired effect.
この新しい段落は、
File()
が返す*os.File
が、元のnet.Conn
とは異なるファイルディスクリプタを持つことを明確に述べています。そして、この複製されたファイルディスクリプタを介して元の接続のプロパティ(例: ソケットオプション)を変更しようとしても、それが元の接続に影響を与えるとは限らないという重要な警告を追加しています。これは、dup()
システムコールのセマンティクスと、ファイルディスクリプタの独立性に関する潜在的な誤解を解消するためのものです。
これらの変更により、File()
メソッドの挙動、特にブロッキングモードへの設定とファイルディスクリプタの独立性に関する情報がより詳細かつ正確になり、開発者がこのメソッドをより安全に利用できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/5416e6e916b56160b9b3b4f4d25118cd739dc0dc
- Go Issue #2458 (コミットメッセージで参照されているが、直接的な情報は見つからず。関連する可能性のあるIssue #24942が存在): https://github.com/golang/go/issues/2458 (このIssue番号はGoの公式リポジトリでは見つかりませんでしたが、コミットメッセージに明記されているため記載します。)
- Go CL 6869054: https://golang.org/cl/6869054
参考にした情報源リンク
- Web search results for "golang net.File blocking mode issue 2458": (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGPjy6SWSVwIu9M4EXh9oqqlHl1w7tq-PwpPKAkC3Q8dy8fvrz3f63f9A8eapTOsqNohb9lYTipDZyfulkcU8jwxOYZ-R16kaGISc58e7wC2IRHUitcU0fmIe-32uvnRpKuMpqv)
- 特に、Go Issue 24942 "net: File method of {TCP,UDP,IP,Unix}Conn and {TCP,Unix}Listener should leave the socket in nonblocking mode" (https://github.com/golang/go/issues/24942) が、
net.File()
とブロッキングモードに関する歴史的背景と問題点を理解する上で非常に参考になりました。
- 特に、Go Issue 24942 "net: File method of {TCP,UDP,IP,Unix}Conn and {TCP,Unix}Listener should leave the socket in nonblocking mode" (https://github.com/golang/go/issues/24942) が、