[インデックス 18126] ファイルの概要
このコミットは、src/pkg/net/fd_unix.go
ファイルに対して行われた変更です。このファイルは、Go言語の標準ライブラリである net
パッケージの一部であり、Unix系システムにおけるネットワークファイルディスクリプタ(FD)の管理と、それに関連する低レベルのネットワーク操作(ソケットの接続など)を扱っています。
コミット
commit 672525a56e7d326ec986bc330a7accd8ec0395f4
Author: Ian Lance Taylor <iant@golang.org>
Date: Sat Dec 28 09:37:54 2013 -0800
net: work around Solaris connect issue when server closes socket
On Solaris, if you do a in-progress connect, and then the
server accepts and closes the socket, the client's later
attempt to complete the connect will fail with EINVAL. Handle
this case by assuming that the connect succeeded. This code
is weird enough that it is implemented as Solaris-only so that
it doesn't hide a real error on a different OS.
Update #6828
R=golang-codereviews, bradfitz, dave
CC=golang-codereviews
https://golang.org/cl/46160043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/672525a56e7d326ec986bc330a7accd8ec0395f4
元コミット内容
このコミットは、Go言語の net
パッケージにおいて、Solarisオペレーティングシステム特有の connect
システムコールに関する問題を回避するための修正を導入しています。具体的には、クライアントがノンブロッキング connect
を実行中に、サーバーが接続を受け入れた直後にソケットを閉じてしまうと、クライアント側で EINVAL
(Invalid argument) エラーが発生する問題に対応しています。この修正では、このような特定の状況下で EINVAL
エラーが発生した場合、接続が成功したとみなすことで問題を回避しています。この回避策はSolarisに限定されており、他のOSでの予期せぬエラーを隠蔽しないように配慮されています。
変更の背景
この変更の背景には、GoプログラムがSolaris上でネットワーク接続を確立する際に直面していた特定のエッジケースがあります。通常、connect
システムコールは、接続が確立されるか、エラーが発生するまでブロックするか、ノンブロッキングモードの場合は EINPROGRESS
や EALREADY
といったエラーを返して処理を継続します。
しかし、Solaris環境では、以下のような特殊なシーケンスで問題が発生していました。
- クライアントがノンブロッキングモードで
connect
システムコールを発行し、接続処理が進行中となる(EINPROGRESS
などが返される)。 - その間にサーバーがクライアントからの接続要求を受け入れ(
accept
)、直後にそのソケットを閉じてしまう。 - クライアントが後から
connect
の完了を試みる(例えば、select
やpoll
でソケットが書き込み可能になったことを検知した後、再度connect
を呼び出すか、ソケットの状態を確認する)。
このシナリオにおいて、Solarisの connect
は予期せず EINVAL
エラーを返していました。一般的な EINVAL
は引数の不正を示しますが、このケースではソケットの状態が原因で発生しており、Goの net
パッケージが期待するエラーハンドリングのフローから外れていました。この問題は、GoのIssue #6828として報告され、このコミットはその解決策として実装されました。
この問題は、特にサーバーが短命な接続を扱う場合や、接続確立直後に何らかの理由でソケットを閉じるようなアプリケーションで顕在化する可能性がありました。Goの net
パッケージは、様々なOSで一貫したネットワークI/Oの抽象化を提供することを目指しているため、このようなOS固有の挙動の違いを吸収する必要がありました。
前提知識の解説
connect
システムコール
connect
は、クライアントがサーバーへの接続を確立するために使用するシステムコールです。
- ブロッキングモード: デフォルトでは、接続が確立されるかエラーが発生するまで呼び出し元をブロックします。
- ノンブロッキングモード: ソケットがノンブロッキングモードに設定されている場合、
connect
はすぐに戻り、接続が進行中であることを示すEINPROGRESS
(またはEALREADY
) エラーを返します。実際の接続完了は、後でソケットが書き込み可能になったことをselect
やpoll
などのI/O多重化メカニズムで検知し、再度ソケットの状態を確認することで判断します。
エラーコード
EINPROGRESS
: ノンブロッキングソケットで接続がすぐに完了せず、処理が進行中であることを示します。EALREADY
: ノンブロッキングソケットで既に接続が進行中であることを示します。EINVAL
: 一般的には、システムコールに渡された引数が無効であることを示します。しかし、OSや特定の状況によっては、より複雑なソケットの状態変化を示すために使用されることがあります。このコミットのケースがまさにそれにあたります。EISCONN
: ソケットが既に接続されていることを示します。
Solarisオペレーティングシステム
Solarisは、Sun Microsystems(現在はOracleが所有)によって開発されたUnix系オペレーティングシステムです。そのネットワークスタックやシステムコールの実装には、他のUnix系OS(LinuxやBSDなど)とは異なる独自の挙動やセマンティクスが存在することがあります。このコミットで扱われている EINVAL
エラーの挙動は、まさにその一例です。
Go言語の net
パッケージ
Goの net
パッケージは、TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための高レベルなインターフェースを提供します。内部的には、OSの提供する低レベルなシステムコール(socket
, bind
, listen
, accept
, connect
など)を抽象化し、Goの並行処理モデル(goroutineとchannel)と統合して、効率的で使いやすいネットワークプログラミングを可能にしています。
fd_unix.go
ファイル
src/pkg/net/fd_unix.go
は、Goの net
パッケージ内で、Unix系システム(Linux, macOS, FreeBSD, Solarisなど)におけるファイルディスクリプタ(FD)の管理と、それらを通じたネットワークI/O操作を実装しているファイルです。netFD
構造体は、ネットワーク接続を表すファイルディスクリプタをラップし、connect
メソッドのような低レベルな操作をカプセル化しています。このファイルは、OS固有のシステムコールを直接呼び出す syscall
パッケージと密接に連携しています。
技術的詳細
このコミットが対処している技術的な問題は、Solarisの connect
システムコールが、特定の競合状態において標準的ではない EINVAL
エラーを返すという点にあります。
Goの net
パッケージの connect
メソッド(fd_unix.go
内)は、ノンブロッキング接続を処理するためにループを使用しています。このループは、connect
システムコールを呼び出し、返されたエラーコードに基づいて処理を分岐します。
一般的なフローは以下の通りです。
connect
を呼び出す。- エラーが
nil
またはsyscall.EISCONN
であれば、接続成功とみなしループを抜ける。 - エラーが
syscall.EINPROGRESS
またはsyscall.EALREADY
であれば、接続が進行中なので、I/O多重化メカニズム(poll
など)でソケットが書き込み可能になるのを待つ。 - その他のエラーであれば、接続失敗としてエラーを返す。
Solarisの特殊なケースでは、サーバーが接続を受け入れた直後にソケットを閉じてしまうと、クライアントが connect
の完了を試みた際に、EINPROGRESS
や EALREADY
ではなく、syscall.EINVAL
が返されていました。Goの既存のロジックでは EINVAL
は一般的なエラーとして扱われ、接続失敗と判断されていました。しかし、この特定のシナリオでは、EINVAL
は実際には接続が一時的に確立された後にサーバー側で切断されたことを示唆しており、クライアント側から見れば「接続は試みられたが、すぐに終了した」という状態に近いものでした。
このコミットの解決策は、このSolaris特有の EINVAL
を「成功」とみなすことです。なぜなら、この状況で EINVAL
が返された場合、ソケットは実際には接続可能な状態になっており、その後のソケットへの書き込み操作は即座にEOF(End Of File)を返すことが期待されるからです。これは、接続が確立された直後に相手が切断した場合の一般的な挙動と一致します。
この回避策がSolarisに限定されているのは非常に重要です。他のOSで EINVAL
が返された場合、それは本当に引数の不正や他の深刻な問題を示している可能性が高く、それを成功とみなしてしまうと、本来検出されるべきエラーが隠蔽されてしまうためです。runtime.GOOS == "solaris"
というチェックを入れることで、この特殊な挙動がSolaris環境でのみ適用されるようにしています。
コアとなるコードの変更箇所
変更は src/pkg/net/fd_unix.go
ファイルの func (fd *netFD) connect(la, ra syscall.Sockaddr) error
メソッド内で行われています。
--- a/src/pkg/net/fd_unix.go
+++ b/src/pkg/net/fd_unix.go
@@ -80,6 +80,16 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr) error {
if err == nil || err == syscall.EISCONN {
break
}
+
+ // On Solaris we can see EINVAL if the socket has
+ // already been accepted and closed by the server.
+ // Treat this as a successful connection--writes to
+ // the socket will see EOF. For details and a test
+ // case in C see http://golang.org/issue/6828.
+ if runtime.GOOS == "solaris" && err == syscall.EINVAL {
+ break
+ }
+
if err != syscall.EINPROGRESS && err != syscall.EALREADY && err != syscall.EINTR {
return err
}
コアとなるコードの解説
追加されたコードブロックは以下の通りです。
// On Solaris we can see EINVAL if the socket has
// already been accepted and closed by the server.
// Treat this as a successful connection--writes to
// the socket will see EOF. For details and a test
// case in C see http://golang.org/issue/6828.
if runtime.GOOS == "solaris" && err == syscall.EINVAL {
break
}
このコードは、connect
システムコールがエラーを返した場合の処理フローに挿入されています。
runtime.GOOS == "solaris"
: これは、現在のGoプログラムがSolarisオペレーティングシステム上で実行されているかどうかをチェックする条件です。このチェックにより、この特殊な回避策がSolaris環境でのみ適用されることが保証されます。他のOSでは、このブロックはスキップされます。err == syscall.EINVAL
: これは、connect
システムコールが返したエラーがEINVAL
であるかどうかをチェックする条件です。
この両方の条件が真である場合、つまり「Solaris上で connect
が EINVAL
を返した」場合に、break
ステートメントが実行されます。break
は、現在の for
ループ(connect
の再試行ループ)を終了させます。これにより、Goランタイムは EINVAL
エラーを「接続成功」として扱い、connect
メソッドから正常にリターンします。
コメントにもあるように、この状況で接続を成功とみなす理由は、「ソケットへの書き込みがEOF(End Of File)を返す」ためです。これは、接続が確立された直後に相手側が接続を閉じた場合の一般的なネットワークプロトコルの挙動と一致します。クライアントは接続できたと認識し、データを送信しようとするとすぐに接続が切れていることを検知できるため、アプリケーションレベルでの適切なエラーハンドリングが可能になります。
この修正は、Solarisの connect
の特殊な挙動を吸収し、Goの net
パッケージが異なるOS間で一貫したネットワークI/Oインターフェースを提供するための重要なステップです。
関連リンク
- Go Issue #6828: https://golang.org/issue/6828
- Go CL 46160043: https://golang.org/cl/46160043
参考にした情報源リンク
- Go Issue #6828 (上記に同じ)
- Go CL 46160043 (上記に同じ)
- Web検索: "Solaris connect EINVAL issue golang issue 6828"