[インデックス 15654] ファイルの概要
このコミットは、Go言語のネットワークパッケージにおいて、accept4システムコールがEINVALエラーを返した場合に、従来のacceptシステムコールにフォールバックする処理を追加するものです。これにより、特定のLinuxカーネルバージョンや環境下での互換性と堅牢性が向上します。
コミット
commit 30b89a84acc86fbb6c69b053eb00ce155084d164
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Mar 8 21:18:06 2013 -0800
net: if accept4 returns EINVAL fall back to accept
R=golang-dev, andybalholm, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/7485045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/30b89a84acc86fbb6c69b053eb00ce155084d164
元コミット内容
net: if accept4 returns EINVAL fall back to accept
R=golang-dev, andybalholm, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/7485045
変更の背景
Go言語のネットワークパッケージは、新しい接続を受け入れる際に、Linux環境では通常accept4システムコールを使用します。accept4は、標準のacceptシステムコールに加えて、ソケットのプロパティ(非ブロッキングモードやクローズ・オン・エグゼックフラグなど)をアトミックに設定できる利点があります。
しかし、accept4システムコールはLinuxカーネルバージョン2.6.28で導入された比較的新しい機能です。そのため、古いカーネルバージョンや、特定の環境設定によってはaccept4が利用できない場合があります。
これまでのGoのコードでは、accept4が利用できない場合にENOSYS(Function not implemented)エラーが返されることを想定し、その場合にのみ従来のacceptシステムコールにフォールバックするロジックが実装されていました。
しかし、一部の環境では、accept4がENOSYSではなくEINVAL(Invalid argument)エラーを返すケースが報告されました。これは、accept4システムコール自体は存在するものの、渡されたフラグ(SOCK_NONBLOCK|SOCK_CLOEXEC)の組み合わせが、そのカーネルバージョンや特定の環境設定において無効であると判断される場合に発生します。例えば、古いglibcのバージョンや、特定のコンテナ環境などでこのような挙動が見られることがあります。
このコミットは、このようなEINVALエラーが発生した場合にも、適切にacceptシステムコールにフォールバックすることで、Goアプリケーションのネットワーク接続処理の堅牢性と互換性を向上させることを目的としています。
前提知識の解説
システムコール (System Call)
システムコールは、ユーザー空間のプログラムがオペレーティングシステム(OS)のカーネル空間の機能を利用するためのインターフェースです。ファイル操作、プロセス管理、ネットワーク通信など、OSが提供する低レベルな機能にアクセスするために使用されます。
acceptとaccept4システムコール
accept: 標準的なソケットAPIの一つで、リスニングソケットで保留中の接続を受け入れ、新しいソケットディスクリプタを返します。この新しいソケットディスクリプタは、確立された接続を介した通信に使用されます。accept4: Linux固有のシステムコールで、acceptの拡張版です。acceptと同様に接続を受け入れますが、追加のflags引数を受け取ります。このflags引数を使用することで、新しく作成されるソケットに対してSOCK_NONBLOCK(非ブロッキングモード)やSOCK_CLOEXEC(クローズ・オン・エグゼック)などのプロパティをアトミックに設定できます。これにより、複数のシステムコールを呼び出すオーバーヘッドを削減し、競合状態のリスクを低減できます。
SOCK_NONBLOCKとSOCK_CLOEXECフラグ
SOCK_NONBLOCK: ソケットを非ブロッキングモードに設定します。これにより、ソケット操作(読み書きなど)が即座に完了しない場合でも、呼び出し元のプロセスがブロックされることなく、エラー(EAGAINやEWOULDBLOCK)を返して処理を続行できます。SOCK_CLOEXEC: クローズ・オン・エグゼックフラグを設定します。このフラグが設定されたファイルディスクリプタは、exec系のシステムコール(新しいプログラムを実行する際に使用される)が呼び出されたときに自動的に閉じられます。これにより、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、リソースリークやセキュリティ上の問題を回避できます。
エラーコード ENOSYSとEINVAL
システムコールが失敗した場合、通常はエラーコードを返します。Go言語では、これらのエラーコードはsyscallパッケージの定数として表現されます。
ENOSYS(No such system call / Function not implemented): このエラーは、呼び出されたシステムコールが現在のカーネルやオペレーティングシステムでサポートされていない場合に返されます。例えば、古いLinuxカーネルでaccept4を呼び出そうとした場合に発生します。EINVAL(Invalid argument): このエラーは、システムコールに渡された引数が無効である場合に返されます。accept4の場合、渡されたflags引数の値が、そのカーネルバージョンや環境において無効な組み合わせであると判断された場合にEINVALが返されることがあります。これは、システムコール自体は存在するものの、特定の機能フラグがサポートされていない状況を示唆します。
技術的詳細
Go言語のnetパッケージは、クロスプラットフォームで動作するように設計されています。そのため、特定のOS固有のシステムコールを使用する際には、そのシステムコールが利用できない場合のフォールバックメカニズムを実装する必要があります。
このコミットが対象としているのは、src/pkg/net/sock_cloexec.goファイル内のaccept関数です。この関数は、内部的にsyscall.Accept4を呼び出して新しい接続を受け入れようとします。
元のコードでは、syscall.Accept4がエラーを返した場合に、そのエラーがsyscall.ENOSYSであるかどうかをチェックしていました。ENOSYSであれば、accept4が利用できないと判断し、従来のsyscall.Acceptにフォールバックしていました。
しかし、前述の通り、一部の環境ではaccept4がEINVALを返すことが問題となりました。これは、SOCK_NONBLOCK|SOCK_CLOEXECというフラグの組み合わせが、その環境のaccept4の実装では無効と判断されるためです。この場合、ENOSYSではないため、フォールバックせずにエラーがそのまま返され、ネットワーク接続が確立できない問題が発生していました。
このコミットでは、この問題を解決するために、フォールバックの条件にEINVALエラーも追加しました。これにより、accept4がENOSYSまたはEINVALのいずれかを返した場合に、安全にacceptシステムコールに切り替えることができるようになります。
この変更は、Goアプリケーションがより広範なLinux環境で安定して動作することを保証し、特にコンテナ環境や古いカーネルバージョンを使用しているシステムでの互換性を向上させます。
コアとなるコードの変更箇所
変更はsrc/pkg/net/sock_cloexec.goファイルにあります。
--- a/src/pkg/net/sock_cloexec.go
+++ b/src/pkg/net/sock_cloexec.go
@@ -44,8 +44,8 @@ func sysSocket(f, t, p int) (int, error) {
func accept(fd int) (int, syscall.Sockaddr, error) {
nfd, sa, err := syscall.Accept4(fd, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
// The accept4 system call was introduced in Linux 2.6.28. If
- // we get an ENOSYS error, fall back to using accept.
- if err == nil || err != syscall.ENOSYS {
+ // we get an ENOSYS or EINVAL error, fall back to using accept.
+ if err == nil || (err != syscall.ENOSYS && err != syscall.EINVAL) {
return nfd, sa, err
}
コアとなるコードの解説
変更された行は以下の部分です。
- if err == nil || err != syscall.ENOSYS {
+ if err == nil || (err != syscall.ENOSYS && err != syscall.EINVAL) {
元のコードでは、syscall.Accept4の呼び出し結果であるerrがnil(エラーなし)であるか、またはsyscall.ENOSYSではない場合に、accept4の結果をそのまま返していました。つまり、ENOSYSの場合にのみフォールバックしていました。
変更後のコードでは、条件がerr == nil || (err != syscall.ENOSYS && err != syscall.EINVAL)となりました。
この条件は以下のように解釈できます。
err == nil:accept4が成功した場合。この場合はそのままaccept4の結果を返します。(err != syscall.ENOSYS && err != syscall.EINVAL):accept4がエラーを返したが、そのエラーがENOSYSでもEINVALでもない場合。この場合も、accept4の結果をそのまま返します。これは、ENOSYSやEINVAL以外のエラーは、accept4が正しく動作しているが、一時的な問題(例:EMFILEファイルディスクリプタ不足など)が発生したと判断し、フォールバックせずにエラーを上位に伝えるためです。
したがって、この条件の否定、つまりフォールバックが発生する条件は以下のようになります。
err != nil && (err == syscall.ENOSYS || err == syscall.EINVAL):accept4がエラーを返し、そのエラーがENOSYSまたはEINVALのいずれかである場合。この場合にのみ、コードはaccept4の呼び出しをスキップし、その後の行でsyscall.Acceptにフォールバックします。
この変更により、accept4がEINVALを返した場合でも、Goのネットワークスタックが適切にacceptに切り替わり、アプリケーションの安定性が向上します。
関連リンク
- Go言語の
netパッケージ: https://pkg.go.dev/net syscallパッケージ: https://pkg.go.dev/syscallaccept4manページ: https://man7.org/linux/man-pages/man2/accept4.2.html
参考にした情報源リンク
accept4system call: https://stackoverflow.com/questions/10057704/what-is-the-purpose-of-accept4-system-callEINVALerror foraccept4: https://github.com/golang/go/issues/5923 (Go issue tracker, related discussions)- Linux kernel documentation on
accept4: https://www.kernel.org/doc/man-pages/online/pages/man2/accept4.2.html