[インデックス 14237] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるSendfile()
システムコールに、データ競合検出器のためのアノテーションを追加するものです。具体的には、各OS固有のSendfile
関数の名前を非公開のsendfile
に変更し、syscall_unix.go
にデータ競合検出器の同期プリミティブを呼び出す新しい公開Sendfile
ラッパー関数を導入しています。これにより、Sendfile
システムコール使用時のデータ競合の検出精度が向上します。
コミット
commit c242aa34cc3781dcbd53f6e6bd952d357656cd11
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Oct 29 23:15:06 2012 +0400
syscalls: annotate Sendfile() for race detector
Fixes #4306.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/6816054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c242aa34cc3781dcbd53f6e6bd952d357656cd11
元コミット内容
syscalls: annotate Sendfile() for race detector
Fixes #4306.
変更の背景
Go言語の並行処理モデルでは、複数のGoroutineが共有リソースにアクセスする際にデータ競合(data race)が発生する可能性があります。データ競合は、少なくとも1つのアクセスが書き込みであり、かつ適切な同期メカニズムが使用されていない場合に、予測不能な動作やバグを引き起こす原因となります。Goには、このようなデータ競合をランタイムで検出するための強力なツールである「データ競合検出器(Race Detector)」が組み込まれています。
Sendfile()
システムコールは、ファイルディスクリプタ間でデータを効率的に転送するための低レベルなI/O操作です。このような低レベルのシステムコールは、内部的にカーネルの共有リソースにアクセスすることがあり、Goのランタイムやデータ競合検出器がその操作を正確に追跡できない場合、誤った競合の報告や、本来検出されるべき競合の見逃しが発生する可能性があります。
このコミットは、Sendfile()
システムコールがデータ競合検出器によって適切に監視され、正確な競合検出が行われるようにするためのアノテーションを追加することを目的としています。コミットメッセージにあるFixes #4306
は、この変更が解決する特定の課題(おそらくSendfile
に関連するデータ競合の誤検知や未検知の問題)を示しています。
前提知識の解説
Goのデータ競合検出器 (Race Detector)
Goのデータ競合検出器は、Goプログラムの並行処理におけるデータ競合を特定するための動的解析ツールです。go build -race
、go run -race
、go test -race
のように-race
フラグを付けてビルドまたは実行することで有効になります。有効にすると、プログラムの実行中にメモリへのアクセスを監視し、複数のGoroutineが同期なしに同じメモリ位置にアクセスし、少なくとも1つが書き込みを行う場合に警告を発します。これにより、発見が困難な並行処理のバグを特定し、修正するのに役立ちます。
sendfile
システムコール
sendfile
は、Unix系OSで提供されるシステムコールの一つで、あるファイルディスクリプタから別のファイルディスクリプタへデータを直接転送するために使用されます。特に、ファイルからネットワークソケットへのデータ転送において非常に効率的です。通常のread()
とwrite()
の組み合わせでは、データがカーネル空間からユーザー空間へ、そして再びカーネル空間へとコピーされる必要がありますが、sendfile
はデータ転送をカーネル内部で完結させることで、これらの余分なデータコピーを排除します(「ゼロコピー」と呼ばれる最適化)。これにより、CPUオーバーヘッドが削減され、I/Oスループットが向上します。
Goのsyscall
パッケージは、OSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。syscall.Sendfile
関数は、このsendfile
システムコールをGoから呼び出すためのラッパーです。
raceReleaseMerge
raceReleaseMerge
は、Goのランタイム内部、特にデータ競合検出器のコンテキストで使用される可能性のある関数です。これはGoの標準ライブラリの公開APIではありません。データ競合検出器は、共有メモリへのアクセスを追跡し、適切な同期プリミティブ(ミューテックス、チャネルなど)が使用されているかを検証します。しかし、Sendfile
のような低レベルのシステムコールは、OSカーネル内で動作するため、Goランタイムがその内部的なメモリ操作を直接監視することは困難です。
raceReleaseMerge
のような関数は、特定のメモリ領域(このコミットではioSync
という変数)に対する操作が、データ競合検出器によって「解放」された(つまり、その操作が完了し、他のGoroutineがそのメモリ領域に安全にアクセスできるようになった)ことを通知するために使用されると考えられます。これにより、データ競合検出器は、システムコールのような外部の操作によって引き起こされる可能性のある競合状態を、より正確に理解し、報告できるようになります。これは、データ競合検出器が誤検知を減らし、真の競合を見逃さないようにするための「アノテーション」または「ヒント」として機能します。
技術的詳細
このコミットの主要な変更は、Goのsyscall
パッケージにおけるSendfile
関数の実装に、データ競合検出器のための明示的な同期アノテーションを追加することです。
-
OS固有の
Sendfile
関数のリネーム:src/pkg/syscall/syscall_darwin.go
,src/pkg/syscall/syscall_freebsd_*.go
,src/pkg/syscall/syscall_linux_*.go
,src/pkg/syscall/syscall_netbsd.go
,src/pkg/syscall/syscall_openbsd.go
,src/pkg/syscall/zsyscall_linux_*.go
といった各OSおよびアーキテクチャ固有のファイルで定義されていたSendfile
関数が、すべて小文字始まりのsendfile
にリネームされました。 例:func Sendfile(...)
からfunc sendfile(...)
へ。 これにより、これらの関数はGoのパッケージの可視性ルールに従い、syscall
パッケージの外部からは直接呼び出せない非公開関数となりました。 -
syscall_unix.go
における新しい公開Sendfile
ラッパー関数の導入:src/pkg/syscall/syscall_unix.go
ファイルに、新たに公開関数Sendfile
が追加されました。この新しいSendfile
関数は、各OS固有の非公開sendfile
関数を呼び出すラッパーとして機能します。 -
データ競合検出器のアノテーションの追加: 新しい公開
Sendfile
ラッパー関数の中に、以下のコードが追加されました。func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) } return sendfile(outfd, infd, offset, count) }
raceenabled
は、Goのデータ競合検出器が有効になっているかどうかを示すグローバル変数です。検出器が有効な場合にのみ、内部的なアノテーションが実行されます。raceReleaseMerge(unsafe.Pointer(&ioSync))
が呼び出されています。ここでioSync
はvar ioSync int64
として定義されたグローバル変数です。unsafe.Pointer(&ioSync)
は、ioSync
変数のメモリアドレスをunsafe.Pointer
型に変換してraceReleaseMerge
に渡しています。- この
raceReleaseMerge
の呼び出しは、Sendfile
システムコールが実行される際に、ioSync
という共有リソースに対する操作がデータ競合検出器によって適切に「解放」されたことを通知する役割を果たします。これにより、Sendfile
のような低レベルのI/O操作が引き起こす可能性のあるデータ競合を、検出器がより正確に追跡できるようになります。これは、システムコールが内部的に共有状態を変更する際に、データ競合検出器がその変更を正しく認識し、誤検知や見逃しを防ぐための重要なステップです。
この変更により、Goのデータ競合検出器はSendfile
システムコールに関連する並行処理の挙動をより正確に分析できるようになり、開発者はSendfile
を使用するアプリケーションにおけるデータ競合の問題をより効果的に特定・修正できるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
-
src/pkg/syscall/syscall_darwin.go
-
src/pkg/syscall/syscall_freebsd_386.go
-
src/pkg/syscall/syscall_freebsd_amd64.go
-
src/pkg/syscall/syscall_freebsd_arm.go
-
src/pkg/syscall/syscall_linux_386.go
-
src/pkg/syscall/syscall_linux_amd64.go
-
src/pkg/syscall/syscall_linux_arm.go
-
src/pkg/syscall/syscall_netbsd.go
-
src/pkg/syscall/syscall_openbsd.go
-
src/pkg/syscall/zsyscall_linux_386.go
-
src/pkg/syscall/zsyscall_linux_amd64.go
-
src/pkg/syscall/zsyscall_linux_arm.go
- これらのファイルでは、既存の公開関数
Sendfile
が非公開関数sendfile
にリネームされています。また、//sys
ディレクティブを使用している箇所も同様にリネームされています。
- これらのファイルでは、既存の公開関数
-
src/pkg/syscall/syscall_unix.go
- このファイルに、新しい公開関数
Sendfile
が追加されました。この関数は、非公開のsendfile
関数を呼び出すラッパーであり、データ競合検出器が有効な場合にraceReleaseMerge
を呼び出すロジックが含まれています。 var ioSync int64
というグローバル変数もこのファイルで定義されています。
- このファイルに、新しい公開関数
コアとなるコードの解説
最も重要な変更はsrc/pkg/syscall/syscall_unix.go
にあります。
diff --git a/src/pkg/syscall/syscall_unix.go b/src/pkg/syscall/syscall_unix.go
index 978350829b..fee1fc491f 100644
--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -143,4 +143,11 @@ func Write(fd int, p []byte) (n int, err error) {
return write(fd, p)
}
+func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
+ if raceenabled {
+ raceReleaseMerge(unsafe.Pointer(&ioSync))
+ }
+ return sendfile(outfd, infd, offset, count)
+}
+
+var ioSync int64
このコードスニペットは、以下の重要な変更を示しています。
-
新しい公開
Sendfile
関数の追加:func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
この関数は、Goのユーザーがsyscall.Sendfile
として呼び出す新しいエントリポイントです。 -
データ競合検出器のアノテーション:
if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) }
この部分が、データ競合検出器のためのアノテーションです。raceenabled
は、Goプログラムが-race
フラグ付きでビルド・実行されている場合にtrue
となるブール変数です。これにより、競合検出器が有効な場合にのみ、このアノテーションが実行されます。raceReleaseMerge
は、Goランタイムの内部関数であり、データ競合検出器に対して、特定のメモリ領域(ここではioSync
変数が指すメモリ)に対する操作が完了し、そのメモリが他のGoroutineに対して「解放」されたことを通知します。これは、Sendfile
システムコールが内部的に共有リソースを操作する際に、データ競合検出器がその操作を正しく追跡し、誤検知や見逃しを防ぐための「ヒント」として機能します。unsafe.Pointer(&ioSync)
は、ioSync
変数のメモリアドレスをunsafe.Pointer
型に変換して渡しています。これは、raceReleaseMerge
が特定のメモリ位置を識別するために必要です。
-
非公開
sendfile
関数の呼び出し:return sendfile(outfd, infd, offset, count)
この行は、実際にOSのsendfile
システムコールを呼び出す、各OS固有の非公開sendfile
関数(以前は公開Sendfile
だったもの)を呼び出しています。新しい公開Sendfile
関数は、この非公開関数をラップし、その前後にデータ競合検出器のアノテーションを追加する役割を担っています。 -
ioSync
変数の定義:var ioSync int64
このioSync
変数は、raceReleaseMerge
関数に渡されるダミーの共有リソースとして機能します。この変数の値自体は重要ではなく、そのメモリアドレスがデータ競合検出器への同期ポイントとして使用されます。
この変更により、Sendfile
システムコールが実行されるたびに、データ競合検出器はioSync
に関連する同期イベントを記録し、Sendfile
の内部的な並行動作をより正確にモデル化できるようになります。これにより、Sendfile
を使用するGoプログラムにおけるデータ競合の検出精度が向上し、より堅牢な並行アプリケーションの開発に貢献します。
関連リンク
- Go CL 6816054: https://golang.org/cl/6816054
参考にした情報源リンク
- Go Race Detector: https://go.dev/blog/race-detector
- Go Race Detector (another source): https://betterprogramming.pub/how-to-detect-race-conditions-in-go-with-the-race-detector-f9121212c2e
- Go
syscall.Sendfile
documentation: https://pkg.go.dev/syscall#Sendfile - Understanding
sendfile()
: https://dev.to/felipecrs/understanding-sendfile-in-linux-2021-302 - Go Concurrency and Race Conditions: https://medium.com/golang-learn/go-concurrency-and-race-conditions-1c29222223d
- Go
sync/atomic
package: https://pkg.go.dev/sync/atomic - Go
io.Copy
andsendfile
: https://www.reddit.com/r/golang/comments/102120/io_copy_and_sendfile/ - Go
golang.org/x/sys
package: https://pkg.go.dev/golang.org/x/sys