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

[インデックス 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操作が即座に完了しない場合でも、呼び出し元のプログラムの実行をブロックしません。操作が完了していない場合は、エラー(例: EAGAINEWOULDBLOCK)を返したり、読み書き可能なバイト数を返したりします。プログラムはI/O操作の完了を待つ間に他のタスクを実行できます。非ブロッキングI/Oは、イベント駆動型プログラミングや、多数の同時接続を効率的に処理するネットワークサーバーで広く利用されます。

Goのnetパッケージは、内部的に非ブロッキングI/OとGoルーチンのスケジューリングを組み合わせて、開発者にはブロッキングI/Oのように見えるAPIを提供しています。しかし、File()メソッドのように基盤となるファイルディスクリプタを直接扱う場合、そのファイルディスクリプタのブロッキングモードが重要になります。

4. dup()システムコール

dup()(またはdup2())は、Unix系オペレーティングシステムで提供されるシステムコールです。既存のファイルディスクリプタの複製を作成します。複製されたファイルディスクリプタは、元のファイルディスクリプタと同じオープンファイル記述(ファイルオフセット、ファイルステータスフラグなど)を参照します。これにより、同じファイルやソケットに対して複数のファイルディスクリプタからアクセスできるようになります。

net.ConnFile()メソッドがdup()を使用するのは、元のネットワーク接続のファイルディスクリプタをそのまま返すのではなく、そのコピーを返すことで、*os.Fileの操作が元のnet.Connの動作に直接影響を与えないようにするためです。

技術的詳細

net.Connインターフェースを実装する具体的な型(例: *net.TCPConn)は、内部にnetFDという構造体を持っています。このnetFDが、実際のネットワークソケットのファイルディスクリプタを管理しています。

conn型のFile()メソッドは、このnetFDdup()メソッドを呼び出します。netFD.dup()の役割は以下の通りです。

  1. ファイルディスクリプタの複製: dup()システムコールを使用して、元のネットワークソケットのファイルディスクリプタの複製を作成します。これにより、File()が返す*os.Fileは、元のnet.Connとは異なるファイルディスクリプタを持つことになります。
  2. ブロッキングモードへの設定: 複製されたファイルディスクリプタをブロッキングモードに設定します。これは、os.Fileが通常ブロッキングI/Oを期待するためです。Goのnetパッケージは、内部で非ブロッキングI/OとGoルーチンのスケジューリングを組み合わせていますが、os.Fileとして公開される際には、一般的なファイルI/Oのセマンティクスに合わせるためにブロッキングモードが選択されます。
  3. *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点です。

  1. 最初の行の修正:

    • 変更前: // 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()が新しいファイルディスクリプタを作成し、それをブロッキングモードに設定するという内部的な挙動を反映しています。
  2. 新しい段落の追加:

    // 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()メソッドの挙動、特にブロッキングモードへの設定とファイルディスクリプタの独立性に関する情報がより詳細かつ正確になり、開発者がこのメソッドをより安全に利用できるようになりました。

関連リンク

参考にした情報源リンク

  • 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()とブロッキングモードに関する歴史的背景と問題点を理解する上で非常に参考になりました。