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

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

このコミットは、Goランタイムのデータ競合検出器(Race Detector)に関連するテストケースを追加するものです。具体的には、構造体の埋め込みフィールドに対するメソッド呼び出し(いわゆる「メソッドサンク」)がデータ競合を引き起こす可能性のあるシナリオと、そうでないシナリオを検証するためのテストがsrc/pkg/runtime/race/testdata/mop_test.goに追加されています。

コミット

commit d660688f14baf29a84d1d7cbeea9eae92c15c60f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Jun 17 17:59:53 2013 +0400

    runtime/race: add tests for method thunks
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/10257043
---
 src/pkg/runtime/race/testdata/mop_test.go | 87 +++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/src/pkg/runtime/race/testdata/mop_test.go b/src/pkg/runtime/race/testdata/mop_test.go
index 9a0cb81f53..b5eecd9fdf 100644
--- a/src/pkg/runtime/race/testdata/mop_test.go
+++ b/src/pkg/runtime/race/testdata/mop_test.go
@@ -1811,3 +1811,90 @@ And Brutus is an honourable man.`
 	}\n 	_ = res\n }\n+\n+type Base int\n+\n+func (b *Base) Foo() int {\n+\treturn 42\n+}\n+\n+func (b Base) Bar() int {\n+\treturn int(b)\n+}\n+\n+func TestNoRaceMethodThunk(t *testing.T) {\n+\ttype Derived struct {\n+\t\tpad int\n+\t\tBase\n+\t}\n+\tvar d Derived\n+\tdone := make(chan bool)\n+\tgo func() {\n+\t\t_ = d.Foo()\n+\t\tdone <- true\n+\t}()\n+\td = Derived{}\n+\t<-done\n+}\n+\n+func TestRaceMethodThunk(t *testing.T) {\n+\ttype Derived struct {\n+\t\tpad int\n+\t\t*Base\n+\t}\n+\tvar d Derived\n+\tdone := make(chan bool)\n+\tgo func() {\n+\t\t_ = d.Foo()\n+\t\tdone <- true\t}()\n+\td = Derived{}\n+\t<-done\n+}\n+\n+func TestRaceMethodThunk2(t *testing.T) {\n+\ttype Derived struct {\n+\t\tpad int\n+\t\tBase\n+\t}\n+\tvar d Derived\n+\tdone := make(chan bool)\n+\tgo func() {\n+\t\t_ = d.Bar()\n+\t\tdone <- true\n+\t}()\n+\td = Derived{}\n+\t<-done\n+}\n+\n+func TestRaceMethodThunk3(t *testing.T) {\n+\ttype Derived struct {\n+\t\tpad int\n+\t\t*Base\n+\t}\n+\tvar d Derived\n+\td.Base = new(Base)\n+\tdone := make(chan bool)\n+\tgo func() {\n+\t\t_ = d.Bar()\n+\t\tdone <- true\n+\t}()\n+\td.Base = new(Base)\n+\t<-done\n+}\n+\n+func TestRaceMethodThunk4(t *testing.T) {\n+\ttype Derived struct {\n+\t\tpad int\n+\t\t*Base\n+\t}\n+\tvar d Derived\n+\td.Base = new(Base)\n+\tdone := make(chan bool)\n+\tgo func() {\n+\t\t_ = d.Bar()\n+\t\tdone <- true\n+\t}()\n+\t*(*int)(d.Base) = 42\n+\t<-done\n+}\n```

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

[https://github.com/golang/go/commit/d660688f14baf29a84d1d7cbeea9eae92c15c60f](https://github.com/golang/go/commit/d660688f14baf29a84d1d7cbeea9eae92c15c60f)

## 元コミット内容

runtime/race: add tests for method thunks

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/10257043

## 変更の背景

Go言語のデータ競合検出器(Race Detector)は、並行処理におけるデータ競合を検出するための強力なツールです。しかし、Goの言語機能の一つである「構造体の埋め込み(embedding)」と、それに伴う「メソッドサンク(method thunks)」の挙動は、通常の変数アクセスとは異なる形でメモリにアクセスする可能性があります。

このコミットが作成された背景には、GoのRace Detectorが、構造体の埋め込みによって生成されるメソッドサンクを介したメモリアクセスに対して、適切にデータ競合を検出できるかどうかの検証が必要だったという点があります。特に、ポインタレシーバを持つメソッドと値レシーバを持つメソッド、そして埋め込みフィールドが値型かポインタ型かによって、メモリアクセスのセマンティクスが変化するため、これらの組み合わせがデータ競合検出に与える影響を網羅的にテストする必要がありました。

このテストの追加は、Race Detectorの堅牢性を高め、Goプログラムの並行処理における潜在的なバグをより確実に特定できるようにすることを目的としています。

## 前提知識の解説

### 1. Goのデータ競合検出器 (Race Detector)

GoのRace Detectorは、Goプログラムの実行中に発生するデータ競合(data race)を検出するためのツールです。データ競合とは、複数のGoroutineが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生するバグです。データ競合は、プログラムの予測不能な動作、クラッシュ、または誤った結果を引き起こす可能性があります。

Race Detectorは、プログラムの実行を監視し、メモリアクセスとGoroutineのスケジューリングに関する情報を収集します。そして、これらの情報に基づいて、データ競合のパターンを特定します。Goプログラムは、`go run -race`、`go build -race`、`go test -race`などのコマンドでビルドまたは実行することで、Race Detectorを有効にできます。

### 2. Goの構造体の埋め込み (Struct Embedding)

Go言語では、構造体の中に別の構造体やインターフェースを「埋め込む」ことができます。これは、他の言語における継承に似た機能を提供しますが、Goでは「コンポジション(合成)」として扱われます。埋め込まれた型は、そのフィールドやメソッドを外側の構造体に「昇格(promote)」させます。これにより、外側の構造体のインスタンスから、埋め込まれた型のフィールドやメソッドに直接アクセスできるようになります。

例:
```go
type Base struct {
    Value int
}

func (b Base) GetValue() int {
    return b.Value
}

type Derived struct {
    Base // Base構造体を埋め込み
    AnotherField string
}

func main() {
    d := Derived{Base: Base{Value: 10}, AnotherField: "hello"}
    fmt.Println(d.Value)    // Base.Valueに直接アクセス
    fmt.Println(d.GetValue()) // Base.GetValue()に直接アクセス
}

3. メソッドサンク (Method Thunks)

構造体の埋め込みにおいて、埋め込まれた型のメソッドが外側の構造体から呼び出される際、コンパイラは「メソッドサンク」と呼ばれる小さなラッパー関数を生成することがあります。これは、外側の構造体のインスタンスから埋め込まれた型のインスタンスへのポインタを調整し、適切なレシーバでメソッドを呼び出すためのものです。

特に、埋め込まれたフィールドがポインタ型である場合や、レシーバの型が異なる場合(例:値レシーバのメソッドをポインタレシーバのインスタンスから呼び出す場合など)に、このサンクが生成されることがあります。このサンクは、元のメソッドが期待するレシーバの型に合わせて、レシーバの値を変換したり、ポインタをデリファレンスしたりする役割を担います。

メソッドサンクは、Goのコンパイラが内部的に生成するものであり、開発者が直接意識することは通常ありません。しかし、Race Detectorのようなツールにとっては、このサンクがどのようにメモリにアクセスするかが重要になります。サンクが生成される過程で、元の埋め込みフィールドへのアクセスが発生するため、そのアクセスが並行処理下でデータ競合を引き起こす可能性があるからです。

技術的詳細

このコミットで追加されたテストケースは、GoのRace Detectorがメソッドサンクを介したメモリアクセスを正しく処理できるかを検証しています。テストは、Base型とDerived型を定義し、Derived型がBase型を埋め込む形で構成されています。Base型には、ポインタレシーバのメソッドFoo()と値レシーバのメソッドBar()があります。

テストの主要なポイントは以下の通りです。

  1. Base型の定義:

    • type Base intBase型はintのエイリアスとして定義されています。
    • func (b *Base) Foo() intBase型へのポインタをレシーバとするメソッド。
    • func (b Base) Bar() intBase型の値をレシーバとするメソッド。
  2. Derived型の定義:

    • type Derived struct { pad int; Base }Base型を値として埋め込むケース。
    • type Derived struct { pad int; *Base }Base型へのポインタを埋め込むケース。
  3. テストシナリオ: 各テスト関数は、Derived型のインスタンスをGoroutine間で共有し、一方のGoroutineで埋め込みフィールドのメソッドを呼び出し、もう一方のGoroutineでDerivedインスタンスまたは埋め込みフィールドにアクセスすることで、データ競合が発生するかどうかを検証しています。

    • TestNoRaceMethodThunk:

      • DerivedBaseを値として埋め込みます。
      • Goroutine内でd.Foo()(ポインタレシーバのメソッド)を呼び出します。
      • メインGoroutineでd = Derived{}Derivedインスタンス全体を書き換えます。
      • このケースでは、d.Foo()の呼び出しはdのコピーに対して行われるため、d自体の書き換えとは競合しないと期待されます。Race Detectorが競合を報告しないことを確認します。
    • TestRaceMethodThunk:

      • Derived*Baseをポインタとして埋め込みます。
      • Goroutine内でd.Foo()(ポインタレシーバのメソッド)を呼び出します。
      • メインGoroutineでd = Derived{}Derivedインスタンス全体を書き換えます。
      • *Baseが埋め込まれているため、d.Foo()の呼び出しはdが保持するポインタを介して行われます。dの書き換えは、このポインタ自体を書き換えるため、競合が発生すると期待されます。Race Detectorが競合を報告することを確認します。
    • TestRaceMethodThunk2:

      • DerivedBaseを値として埋め込みます。
      • Goroutine内でd.Bar()(値レシーバのメソッド)を呼び出します。
      • メインGoroutineでd = Derived{}Derivedインスタンス全体を書き換えます。
      • d.Bar()dのコピーに対して呼び出されるため、d自体の書き換えとは競合しないと期待されます。Race Detectorが競合を報告しないことを確認します。
    • TestRaceMethodThunk3:

      • Derived*Baseをポインタとして埋め込みます。
      • Goroutine内でd.Bar()(値レシーバのメソッド)を呼び出します。
      • メインGoroutineでd.Base = new(Base)と埋め込みポインタを書き換えます。
      • d.Bar()の呼び出しは、d.Baseが指す値にアクセスします。メインGoroutineでのd.Baseの書き換えは、このポインタ自体を書き換えるため、競合が発生すると期待されます。Race Detectorが競合を報告することを確認します。
    • TestRaceMethodThunk4:

      • Derived*Baseをポインタとして埋め込みます。
      • Goroutine内でd.Bar()(値レシーバのメソッド)を呼び出します。
      • メインGoroutineで*(*int)(d.Base) = 42と、埋め込みポインタが指す先の値を書き換えます。
      • d.Bar()の呼び出しは、d.Baseが指す値(int型)を読み取ります。メインGoroutineでのその値への書き込みは、競合が発生すると期待されます。Race Detectorが競合を報告することを確認します。

これらのテストは、Goのコンパイラが生成するメソッドサンクが、埋め込みフィールドの型(値型かポインタ型か)や、呼び出されるメソッドのレシーバの型(値レシーバかポインタレシーバか)に応じて、どのようにメモリにアクセスするかを網羅的に検証しています。これにより、Race Detectorがこれらの複雑なシナリオにおいても、データ競合を正確に検出できることが保証されます。

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

変更はsrc/pkg/runtime/race/testdata/mop_test.goファイルに集中しており、以下の新しいテスト関数と補助的な型定義が追加されています。

type Base int

func (b *Base) Foo() int {
	return 42
}

func (b Base) Bar() int {
	return int(b)
}

func TestNoRaceMethodThunk(t *testing.T) {
	type Derived struct {
		pad int
		Base
	}
	var d Derived
	done := make(chan bool)
	go func() {
		_ = d.Foo()
		done <- true
	}()
	d = Derived{}
	<-done
}

func TestRaceMethodThunk(t *testing.T) {
	type Derived struct {
		pad int
		*Base
	}
	var d Derived
	done := make(chan bool)
	go func() {
		_ = d.Foo()
		done <- true
	}()
	d = Derived{}
	<-done
}

func TestRaceMethodThunk2(t *testing.T) {
	type Derived struct {
		pad int
		Base
	}
	var d Derived
	done := make(chan bool)
	go func() {
		_ = d.Bar()
		done <- true
	}()
	d = Derived{}
	<-done
}

func TestRaceMethodThunk3(t *testing.T) {
	type Derived struct {
		pad int
		*Base
	}
	var d Derived
	d.Base = new(Base)
	done := make(chan bool)
	go func() {
		_ = d.Bar()
		done <- true
	}()
	d.Base = new(Base)
	<-done
}

func TestRaceMethodThunk4(t *testing.T) {
	type Derived struct {
		pad int
		*Base
	}
	var d Derived
	d.Base = new(Base)
	done := make(chan bool)
	go func() {
		_ = d.Bar()
		done <- true
	}()
	*(*int)(d.Base) = 42
	<-done
}

コアとなるコードの解説

追加されたコードは、GoのRace Detectorのテストスイートの一部であり、特に構造体の埋め込みとメソッドサンクに関連するデータ競合の検出能力を検証するために設計されています。

  • Base型とメソッドFoo, Bar:

    • Base intは、基本的な整数型をラップした型です。
    • Foo()はポインタレシーバ*Baseを持つメソッドです。これは、メソッドがBase型のインスタンスのポインタを受け取り、そのポインタが指す元のメモリ位置にアクセスする可能性があることを意味します。
    • Bar()は値レシーバBaseを持つメソッドです。これは、メソッドがBase型のインスタンスのコピーを受け取るため、元のメモリ位置には直接アクセスしないことを意味します。
  • Derived型と埋め込み:

    • 各テスト関数内で定義されるDerived型は、pad intフィールドと、Baseまたは*Baseのいずれかを埋め込みフィールドとして持ちます。
    • Baseを値として埋め込む場合(例: TestNoRaceMethodThunk, TestRaceMethodThunk2)、DerivedインスタンスはBase型の値のコピーを直接含みます。
    • *Baseをポインタとして埋め込む場合(例: TestRaceMethodThunk, TestRaceMethodThunk3, TestRaceMethodThunk4)、DerivedインスタンスはBase型へのポインタを含みます。このポインタが指す先のメモリは、複数のGoroutineからアクセスされる可能性があります。
  • テストロジック: 各テスト関数は、以下の共通パターンに従います。

    1. Derived型の変数dを宣言します。
    2. doneという名前のチャネルを作成し、Goroutineの完了を同期するために使用します。
    3. 新しいGoroutineを起動し、その中でdの埋め込みフィールドのメソッド(FooまたはBar)を呼び出します。
    4. メインGoroutineで、dのインスタンス全体を書き換えたり(d = Derived{})、埋め込みポインタを書き換えたり(d.Base = new(Base))、埋め込みポインタが指す先の値を書き換えたり(*(*int)(d.Base) = 42)します。
    5. doneチャネルからの受信を待ち、Goroutineの完了を待ちます。

これらのテストは、Race Detectorが、埋め込みフィールドの型とメソッドのレシーバの型の組み合わせによって発生する、微妙なデータ競合のシナリオを正確に識別できることを保証します。特に、ポインタレシーバのメソッドがポインタ埋め込みフィールドを介して呼び出される場合や、値レシーバのメソッドがポインタ埋め込みフィールドを介して呼び出され、そのポインタが指す値が別のGoroutineで変更される場合に、Race Detectorが正しく競合を報告することが期待されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goのソースコード(特にsrc/runtime/raceディレクトリ)
  • GoのIssueトラッカーやメーリングリストでの議論(Race Detectorや埋め込みに関するもの)
  • Goのコンパイラとランタイムに関する技術記事やブログポスト
  • データ競合検出器に関する一般的なコンピュータサイエンスの知識
  • Goのテストフレームワークに関する知識
  • Goの並行処理プリミティブ(Goroutine, Channel)に関する知識