[インデックス 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/syscallaccept4
manページ: https://man7.org/linux/man-pages/man2/accept4.2.html
参考にした情報源リンク
accept4
system call: https://stackoverflow.com/questions/10057704/what-is-the-purpose-of-accept4-system-callEINVAL
error 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