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

[インデックス 14510] ファイルの概要

このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)のテストスイートに、特定のバグ(Issue 4453)を再現するための新しいテストケースを追加するものです。具体的には、src/pkg/runtime/race/testdata/mop_test.go ファイルに TestRaceFailingSliceStruct というテスト関数が追加されています。

コミット

commit 58ce93b6bfe1e24539d621ef326876f1f6758cf8
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Nov 27 21:59:08 2012 +0400

    runtime/race: add test case for issue 4453
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6854103

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/58ce93b6bfe1e24539d621ef326876f1f6758cf8

元コミット内容

このコミットの目的は、Go言語のランタイムにおけるデータ競合検出器(Race Detector)が、特定の条件下で発生するデータ競合を正しく検出できないというバグ(Issue 4453)を修正するために、そのバグを再現するテストケースを追加することです。これにより、将来的にこの問題が再発しないように、または修正が正しく機能していることを確認できるようにします。

変更の背景

この変更の背景には、Go言語のデータ競合検出器が、構造体のスライス([]struct)をcopy関数でコピーする際に発生するデータ競合を適切に検出できないという問題(Issue 4453)がありました。データ競合は並行プログラミングにおける深刻なバグの一種であり、プログラムの予測不能な動作やクラッシュを引き起こす可能性があります。GoのRace Detectorは、このような問題を開発段階で早期に発見するための重要なツールです。しかし、特定のケースで検出が漏れることが判明したため、その問題を修正し、将来的に回帰しないようにするためのテストケースが必要とされました。

前提知識の解説

Go言語のデータ競合 (Data Race)

データ競合とは、複数のゴルーチン(Goの軽量スレッド)が同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生する状況を指します。データ競合は、プログラムの動作を非決定的にし、デバッグが非常に困難なバグの温床となります。

Go Race Detector

Go言語には、ビルド時に-raceフラグを付けることで有効化できる組み込みのデータ競合検出器があります。これは、実行時にメモリへのアクセスを監視し、データ競合のパターンを検出すると警告を出力します。開発者が並行プログラムのバグを発見する上で非常に強力なツールです。

スライス (Slice)

Goのスライスは、配列のセグメントを参照するデータ構造です。スライスは、基になる配列へのポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。スライスを別のスライスにcopyする操作は、要素の値をコピーしますが、基になる配列が同じであれば、その配列へのアクセスは共有されたままになります。

構造体 (Struct)

Goの構造体は、異なる型のフィールドをまとめた複合データ型です。このコミットで問題となっているのは、int型のフィールドを持つ構造体のスライスです。

copy関数

Goの組み込み関数copy(dst, src []Type)は、srcスライスからdstスライスへ要素をコピーします。コピーされる要素数は、len(src)len(dst)の小さい方です。この操作は、要素の値をコピーするものであり、参照をコピーするものではありません。しかし、スライスが指す基底配列が共有されている場合、copy操作自体は安全でも、その後に異なるゴルーチンが同じ基底配列の異なる部分にアクセスし、かつ書き込み操作が含まれる場合に競合が発生する可能性があります。

技術的詳細

このコミットで追加されたテストケースTestRaceFailingSliceStructは、以下のシナリオを再現します。

  1. Xという構造体を定義します。この構造体はxyという2つのint型フィールドを持ちます。
  2. X型の要素を10個持つスライスxを作成します。
  3. 新しいゴルーチンを起動します。
  4. 新しいゴルーチン内で、X型の要素を10個持つ別のスライスyを作成します。
  5. copy(y, x)を実行し、スライスxの内容をスライスyにコピーします。
  6. メインゴルーチンで、x[1].y = 42という書き込み操作を行います。
  7. 新しいゴルーチンとメインゴルーチンの間で同期を取り、両方の操作が完了するのを待ちます。

このシナリオでは、copy(y, x)が実行された後、xyは異なるスライスヘッダを持つものの、copy操作によってxの基底配列のデータがyの基底配列にコピーされます。問題は、xの基底配列の要素にメインゴルーチンが書き込みを行う一方で、新しいゴルーチンがcopy操作を通じてxの基底配列のデータを読み取っている点にあります。もしRace Detectorがこの競合を検出できない場合、それはバグとなります。

Issue 4453の根本原因は、Race Detectorがスライス内の構造体フィールドへのアクセスを適切に追跡できていなかったことにあります。特に、copyのような一括操作や、構造体内の特定のフィールドへのアクセスが、Race Detectorの監視対象から漏れるケースがあったと考えられます。このテストケースは、そのような特定のアクセスパターンを意図的に作り出し、Race Detectorがそれを検出できるかどうかを検証します。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/race/testdata/mop_test.goファイルに集中しています。

--- a/src/pkg/runtime/race/testdata/mop_test.go
+++ b/src/pkg/runtime/race/testdata/mop_test.go
@@ -1239,6 +1239,22 @@ func TestRaceSliceSlice2(t *testing.T) {
 	<-c
 }
 
+// http://golang.org/issue/4453
+func TestRaceFailingSliceStruct(t *testing.T) {
+	type X struct {
+		x, y int
+	}
+	c := make(chan bool, 1)
+	x := make([]X, 10)
+	go func() {
+		y := make([]X, 10)
+		copy(y, x)
+		c <- true
+	}()
+	x[1].y = 42
+	<-c
+}
+
 func TestRaceStructInd(t *testing.T) {
 	c := make(chan bool, 1)
 	type Item struct {

コアとなるコードの解説

追加されたTestRaceFailingSliceStruct関数は以下の要素で構成されます。

  1. 構造体Xの定義:

    type X struct {
    	x, y int
    }
    

    これは、データ競合の対象となるint型のフィールドxyを持つシンプルな構造体です。

  2. チャネルcの作成:

    c := make(chan bool, 1)
    

    ゴルーチン間の同期のためにバッファ付きチャネルを使用します。これにより、メインゴルーチンは新しいゴルーチンの処理が完了するのを待つことができます。

  3. スライスxの初期化:

    x := make([]X, 10)
    

    X型の要素を10個持つスライスxを作成します。これは、競合が発生する可能性のある共有データ構造の役割を果たします。

  4. 新しいゴルーチンの起動:

    go func() {
    	y := make([]X, 10)
    	copy(y, x)
    	c <- true
    }()
    

    このゴルーチンは、X型の新しいスライスyを作成し、copy(y, x)を実行します。ここで重要なのは、copy関数がxの基底配列からyの基底配列へデータを読み取る点です。この読み取り操作が、メインゴルーチンでのx[1].y = 42という書き込み操作と競合する可能性があります。c <- trueは、このゴルーチンが処理を完了したことをメインゴルーチンに通知します。

  5. メインゴルーチンでの書き込み操作:

    x[1].y = 42
    

    新しいゴルーチンがcopyを実行している間に、メインゴルーチンはスライスxの2番目の要素(インデックス1)のyフィールドに42を書き込みます。この書き込みが、新しいゴルーチンによるxからの読み取りと同時に行われることで、データ競合が発生します。

  6. 同期:

    <-c
    

    メインゴルーチンは、新しいゴルーチンがチャネルcに値を送信するまでブロックします。これにより、新しいゴルーチンがcopy操作を完了するまで、メインゴルーチンが終了しないことが保証され、競合の機会が最大化されます。

このテストケースは、Race Detectorがcopy操作と構造体フィールドへの直接アクセスという組み合わせで発生するデータ競合を正しく検出できることを確認するために設計されています。

関連リンク

参考にした情報源リンク

[インデックス 14510] ファイルの概要

このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)のテストスイートに、特定のバグ(Issue 4453)を再現するための新しいテストケースを追加するものです。具体的には、src/pkg/runtime/race/testdata/mop_test.go ファイルに TestRaceFailingSliceStruct というテスト関数が追加されています。

コミット

commit 58ce93b6bfe1e24539d621ef326876f1f6758cf8
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Nov 27 21:59:08 2012 +0400

    runtime/race: add test case for issue 4453
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6854103

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/58ce93b6bfe1e24539d621ef326876f1f6758cf8

元コミット内容

このコミットの目的は、Go言語のランタイムにおけるデータ競合検出器(Race Detector)が、特定の条件下で発生するデータ競合を正しく検出できないというバグ(Issue 4453)を修正するために、そのバグを再現するテストケースを追加することです。これにより、将来的にこの問題が再発しないように、または修正が正しく機能していることを確認できるようにします。

変更の背景

この変更の背景には、Go言語のデータ競合検出器が、構造体のスライス([]struct)をcopy関数でコピーする際に発生するデータ競合を適切に検出できないという問題(Issue 4453)がありました。データ競合は並行プログラミングにおける深刻なバグの一種であり、プログラムの予測不能な動作やクラッシュを引き起こす可能性があります。GoのRace Detectorは、このような問題を開発段階で早期に発見するための重要なツールです。しかし、特定のケースで検出が漏れることが判明したため、その問題を修正し、将来的に回帰しないようにするためのテストケースが必要とされました。

Issue 4453自体は、GitHubの公開リポジトリでは直接見つかりませんでしたが、コミットメッセージと関連するGerrit Code Review (CL 6854103) の情報から、Goの内部的な課題追跡システムで管理されていた問題であると推測されます。この問題は、copy操作と構造体内のフィールドへのアクセスが絡む複雑なデータ競合シナリオにおいて、Race Detectorが誤検知または見逃しをしていた可能性を示唆しています。

前提知識の解説

Go言語のデータ競合 (Data Race)

データ競合とは、複数のゴルーチン(Goの軽量スレッド)が同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生する状況を指します。データ競合は、プログラムの動作を非決定的にし、デバッグが非常に困難なバグの温床となります。例えば、あるゴルーチンが変数に書き込みを行っている最中に、別のゴルーチンがその変数を読み取ろうとすると、読み取られる値が未定義になる可能性があります。

Go Race Detector

Go言語には、ビルド時に-raceフラグを付けることで有効化できる組み込みのデータ競合検出器があります。これは、実行時にメモリへのアクセスを監視し、データ競合のパターンを検出すると警告を出力します。開発者が並行プログラムのバグを発見する上で非常に強力なツールです。Race Detectorは、メモリへのアクセスをインターセプトし、各アクセスが「ハプンズ・ビフォー」関係(happens-before relationship)に従っているかを追跡することで機能します。これにより、同期されていない並行アクセスを特定します。

スライス (Slice)

Goのスライスは、配列のセグメントを参照するデータ構造です。スライスは、基になる配列へのポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。スライスを別のスライスにcopyする操作は、要素の値をコピーしますが、基になる配列が同じであれば、その配列へのアクセスは共有されたままになります。スライスは動的なサイズ変更が可能であり、Goにおけるコレクションの主要な型です。

構造体 (Struct)

Goの構造体は、異なる型のフィールドをまとめた複合データ型です。このコミットで問題となっているのは、int型のフィールドを持つ構造体のスライスです。構造体は、関連するデータを一つの論理的な単位としてまとめるために使用されます。

copy関数

Goの組み込み関数copy(dst, src []Type)は、srcスライスからdstスライスへ要素をコピーします。コピーされる要素数は、len(src)len(dst)の小さい方です。この操作は、要素の値をコピーするものであり、参照をコピーするものではありません。しかし、スライスが指す基底配列が共有されている場合、copy操作自体は安全でも、その後に異なるゴルーチンが同じ基底配列の異なる部分にアクセスし、かつ書き込み操作が含まれる場合に競合が発生する可能性があります。特に、copyは内部的に要素を一つずつコピーするため、その過程でRace Detectorが各要素へのアクセスを適切に監視できているかが重要になります。

技術的詳細

このコミットで追加されたテストケースTestRaceFailingSliceStructは、以下のシナリオを再現します。

  1. 構造体Xの定義:

    type X struct {
    	x, y int
    }
    

    xyという2つのint型フィールドを持つシンプルな構造体を定義します。これは、データ競合の対象となるメモリ領域を表現します。

  2. チャネルcの作成:

    c := make(chan bool, 1)
    

    ゴルーチン間の同期のためにバッファ付きチャネルを使用します。これにより、メインゴルーチンは新しいゴルーチンの処理が完了するのを待つことができます。バッファ付きチャネルは、送信側が受信側をブロックすることなく、指定された数の値を送信できるため、非同期処理の完了通知に適しています。

  3. スライスxの初期化:

    x := make([]X, 10)
    

    X型の要素を10個持つスライスxを作成します。これは、競合が発生する可能性のある共有データ構造の役割を果たします。このスライスは、基底配列として10個のX構造体を格納できるメモリ領域を確保します。

  4. 新しいゴルーチンの起動:

    go func() {
    	y := make([]X, 10)
    	copy(y, x) // ここでxの基底配列からyの基底配列へデータが読み取られる
    	c <- true
    }()
    

    このゴルーチンは、X型の新しいスライスyを作成し、copy(y, x)を実行します。ここで重要なのは、copy関数がxの基底配列からyの基底配列へデータを読み取る点です。この読み取り操作が、メインゴルーチンでのx[1].y = 42という書き込み操作と競合する可能性があります。c <- trueは、このゴルーチンが処理を完了したことをメインゴルーチンに通知します。copy関数は要素ごとにコピーを行うため、この操作中にxの各要素への読み取りアクセスが発生します。

  5. メインゴルーチンでの書き込み操作:

    x[1].y = 42
    

    新しいゴルーチンがcopyを実行している間に、メインゴルーチンはスライスxの2番目の要素(インデックス1)のyフィールドに42を書き込みます。この書き込みが、新しいゴルーチンによるxからの読み取りと同時に行われることで、データ競合が発生します。具体的には、x[1].yへの書き込みと、copy(y, x)によるx[1]の読み取りが同期なしに並行して行われるため、Race Detectorがこれを検出する必要があります。

  6. 同期:

    <-c
    

    メインゴルーチンは、新しいゴルーチンがチャネルcに値を送信するまでブロックします。これにより、新しいゴルーチンがcopy操作を完了するまで、メインゴルーチンが終了しないことが保証され、競合の機会が最大化されます。この同期メカニズムは、テストケースが確実にデータ競合の状況を作り出すために不可欠です。

Issue 4453の根本原因は、Race Detectorがスライス内の構造体フィールドへのアクセス、特にcopyのような一括操作を介したアクセスを適切に追跡できていなかったことにあります。このテストケースは、そのような特定のアクセスパターンを意図的に作り出し、Race Detectorがそれを検出できるかどうかを検証します。もしRace Detectorがこのテストケースで競合を報告しない場合、それはRace Detector自体のバグを示唆します。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/race/testdata/mop_test.goファイルに集中しています。

--- a/src/pkg/runtime/race/testdata/mop_test.go
+++ b/src/pkg/runtime/race/testdata/mop_test.go
@@ -1239,6 +1239,22 @@ func TestRaceSliceSlice2(t *testing.T) {
 	<-c
 }
 
+// http://golang.org/issue/4453
+func TestRaceFailingSliceStruct(t *testing.T) {
+	type X struct {
+		x, y int
+	}
+	c := make(chan bool, 1)
+	x := make([]X, 10)
+	go func() {
+		y := make([]X, 10)
+		copy(y, x)
+		c <- true
+	}()
+	x[1].y = 42
+	<-c
+}
+
 func TestRaceStructInd(t *testing.T) {
 	c := make(chan bool, 1)
 	type Item struct {

コアとなるコードの解説

追加されたTestRaceFailingSliceStruct関数は、Goのtestingパッケージを利用した標準的なテスト関数です。

  1. type X struct { x, y int }: この行では、xyという2つの整数型フィールドを持つXという名前の構造体を定義しています。この構造体のインスタンスがスライス内に格納され、そのフィールドへのアクセスがデータ競合の対象となります。

  2. c := make(chan bool, 1): バッファサイズが1のブール型チャネルcを作成しています。これは、メインゴルーチンと新しく起動されるゴルーチンとの間で同期を取るために使用されます。新しいゴルーチンが処理を完了したことをメインゴルーチンに通知するために使われます。

  3. x := make([]X, 10): X型の要素を10個格納できるスライスxを作成しています。このスライスは、データ競合が発生する可能性のある共有メモリ領域を表します。初期化時には、各X構造体のフィールドはゼロ値(0)で埋められます。

  4. go func() { ... }(): 新しいゴルーチンを起動しています。このゴルーチン内で並行処理が行われます。

    • y := make([]X, 10): 新しいゴルーチン内で、X型の要素を10個格納できる別のスライスyを作成しています。
    • copy(y, x): ここがデータ競合の核心部分です。スライスxの要素をスライスyにコピーしています。この操作は、xの基底配列からデータを読み取ることを含みます。この読み取り操作が、メインゴルーチンでのxへの書き込みと並行して行われることで、データ競合が発生します。
    • c <- true: copy操作が完了した後、新しいゴルーチンはチャネルctrueを送信します。これにより、メインゴルーチンは新しいゴルーチンがそのタスクを完了したことを知ることができます。
  5. x[1].y = 42: メインゴルーチンで実行されるこの行は、スライスxの2番目の要素(インデックス1)のyフィールドに値42を書き込んでいます。この書き込み操作は、新しいゴルーチンがcopy(y, x)を実行している最中、つまりxの同じメモリ領域を読み取っている最中に発生する可能性があります。これがデータ競合を引き起こす原因となります。

  6. <-c: メインゴルーチンは、チャネルcから値を受信するまでブロックします。これにより、新しいゴルーチンがcopy操作を完了し、チャネルに値を送信するまで、メインゴルーチンが終了しないことが保証されます。この同期は、データ競合が確実に発生する時間枠を確保するために重要です。

このテストケースは、Race Detectorがcopy操作と構造体フィールドへの直接アクセスという組み合わせで発生するデータ競合を正しく検出できることを確認するために設計されています。もしRace Detectorがこのテストで警告を出さない場合、それはRace Detectorの機能に欠陥があることを意味します。

関連リンク

  • Go Issue 4453: このコミットが参照している内部的な課題番号。公開されているGitHubリポジトリでは直接見つからない可能性がありますが、コミットメッセージとGerritの変更リストで言及されています。
  • Go CL 6854103: https://golang.org/cl/6854103 (Gerrit Code Reviewへのリンク) - このコミットに対応するGoのコードレビューページです。ここから、この変更がDmitriy Vyukovによって提案され、golang-devとbradfitzによってレビューされ、最終的に承認・マージされたことが確認できます。

参考にした情報源リンク