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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージにおける、データ競合検出器(Race Detector)使用時のテストの挙動に関する修正です。具体的には、HeaderWriteSubset 関数のメモリ割り当て(allocations)テストが、Race Detectorが有効な環境下でスキップされるように変更されています。これにより、Race Detectorのオーバーヘッドによるテストの不安定化や誤検出を防ぎ、テストの信頼性を向上させています。

コミット

commit 5f1e0fa538991cf2d2f0f48c8e15a3bca3f52918
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Mar 4 08:56:52 2014 -0800

    net/http: disable an alloc test under the race detector
    
    LGTM=dvyukov
    R=dvyukov
    CC=golang-codereviews
    https://golang.org/cl/70200052

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

https://github.com/golang/go/commit/5f1e0fa538991cf2d2f0f48c8e15a3bca3f52918

元コミット内容

net/http: disable an alloc test under the race detector

このコミットメッセージは、net/http パッケージにおいて、メモリ割り当て(allocations)に関するテストを、Goのデータ競合検出器(Race Detector)が有効な場合に無効化することを示しています。

変更の背景

Go言語には、並行処理におけるデータ競合(data race)を検出するための強力なツールである「Race Detector」が組み込まれています。データ競合は、複数のゴルーチンが共有変数に同時にアクセスし、そのうち少なくとも1つが書き込み操作である場合に発生し、予測不能なプログラムの挙動やバグの原因となります。Race Detectorは、コンパイル時に-raceフラグを付与することで有効化され、実行時にメモリアクセスを監視してデータ競合を検出します。

しかし、Race Detectorは、その性質上、実行時にかなりのオーバーヘッドを伴います。メモリ使用量が5〜10倍に増加し、実行時間が2〜20倍に増加する可能性があります。このオーバーヘッドは、特にメモリ割り当ての回数を厳密にチェックするようなパフォーマンス関連のテストにおいて、テスト結果の不安定化や、本来データ競合とは関係のない部分での誤検出を引き起こす可能性があります。

net/http パッケージの TestHeaderWriteSubsetAllocs テストは、http.HeaderWriteSubset メソッドがどれだけメモリを割り当てるかを検証するものです。この種のテストは、非常に厳密なパフォーマンス要件を持つため、Race Detectorのオーバーヘッドがテストの正確性に影響を与える可能性がありました。

このコミットの背景には、Race Detectorが有効な環境下で、このメモリ割り当てテストが期待通りの結果を出さない、あるいは不安定になるという問題があったと考えられます。そのため、Race Detectorが有効な場合にはこのテストをスキップすることで、テストスイート全体の安定性と信頼性を確保することが目的とされています。

前提知識の解説

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

データ競合は、並行プログラミングにおける一般的なバグの一種です。Go言語では、複数のゴルーチン(軽量スレッド)が同じメモリ領域に同時にアクセスし、そのうち少なくとも1つのアクセスが書き込みである場合に発生します。適切な同期メカニズム(ミューテックス、チャネルなど)が使用されていない場合、最終的な値が予測不能になったり、プログラムがクラッシュしたりする可能性があります。

Go Race Detector

Go Race Detectorは、Go 1.1から導入されたデータ競合検出ツールです。go build -racego run -racego test -race のように-raceフラグを付けてコンパイル・実行することで有効になります。Race Detectorは、実行時にメモリアクセスを監視し、データ競合が発生した場合には詳細なレポート(競合が発生したゴルーチンのスタックトレース、アクセスタイプ、ソースコード上の位置など)を出力します。これは、C/C++のThreadSanitizerライブラリをベースにしており、動的解析によって競合を検出します。

testing パッケージとベンチマーク/アロケーションテスト

Go言語の標準ライブラリには、テストを記述するための testing パッケージが含まれています。

  • テスト関数 (TestXxx): func TestXxx(t *testing.T) の形式で記述され、通常の機能テストを行います。
  • ベンチマーク関数 (BenchmarkXxx): func BenchmarkXxx(b *testing.B) の形式で記述され、コードのパフォーマンスを測定します。b.N 回の操作にかかる時間を測定したり、b.ReportAllocs() を呼び出すことで、ベンチマーク実行中のメモリ割り当て回数やバイト数を報告させることができます。
  • t.Skip(): テストをスキップするために使用されます。特定の条件(例: testing.Short() がtrueの場合、特定の環境の場合など)でテストを実行したくない場合に便利です。
  • runtime.GOMAXPROCS(0): 現在の GOMAXPROCS の値(同時に実行できるOSスレッドの最大数)を返します。この値が1より大きい場合、複数のCPUコアが利用可能であり、並行処理のテストにおいて特定の挙動を示す可能性があります。

ビルドタグ(Build Tags)

Go言語では、ソースファイルにビルドタグを記述することで、特定の条件に基づいてファイルをコンパイルに含めるか除外するかを制御できます。ファイル冒頭に // +build tagname の形式で記述します。このコミットでは // +build race が使用されており、これはRace Detectorが有効な場合にのみこのファイルがコンパイルされることを意味します。

技術的詳細

このコミットは、Goの net/http パッケージにおける Header 型の WriteSubset メソッドのメモリ割り当てテスト TestHeaderWriteSubsetAllocs が、Race Detectorが有効な場合にスキップされるように修正しています。

具体的な変更点は以下の通りです。

  1. src/pkg/net/http/header.go の変更:

    • var raceEnabled = false // set by race.go というグローバル変数が追加されました。この変数は、Race Detectorが有効かどうかを示すフラグとして機能します。初期値は false です。
  2. src/pkg/net/http/race.go の新規追加:

    • race.go という新しいファイルが追加されました。このファイルは、ビルドタグ // +build race を持っています。これは、Race Detectorが有効なビルドの場合にのみ、このファイルがコンパイルされることを意味します。
    • このファイルには init() 関数が含まれており、init() 関数内で raceEnabled = true が設定されます。
    • これにより、Race Detectorが有効なビルドでは raceEnabled 変数が true に設定され、それ以外のビルドでは false のままになります。
  3. src/pkg/net/http/header_test.go の変更:

    • TestHeaderWriteSubsetMallocs というテスト関数の名前が TestHeaderWriteSubsetAllocs に変更されました。これは、mallocs(C言語のメモリ割り当て関数)という用語よりも、Goの文脈でより一般的な「allocs」(割り当て)という用語に合わせたものです。
    • テストのスキップ条件に if raceEnabled { t.Skip("skipping test under race detector") } が追加されました。
    • これにより、raceEnabledtrue(すなわちRace Detectorが有効)の場合、このメモリ割り当てテストは実行されずにスキップされます。
    • エラーメッセージも mallocs = %g; want 0 から allocs = %g; want 0 に変更されています。

この修正により、Race Detectorのオーバーヘッドがメモリ割り当てテストの正確性に影響を与えることを防ぎ、テストスイートの安定性を向上させています。Race Detectorはデータ競合の検出に特化しており、メモリ割り当ての厳密なカウントは本来の目的とは異なるため、このようなテストはRace Detectorの実行時にはスキップするのが適切であるという判断がなされたと考えられます。

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

src/pkg/net/http/header.go

--- a/src/pkg/net/http/header.go
+++ b/src/pkg/net/http/header.go
@@ -13,6 +13,8 @@ import (
 	"time"
 )
 
+var raceEnabled = false // set by race.go
+
 // A Header represents the key-value pairs in an HTTP header.
 type Header map[string][]string
 

src/pkg/net/http/header_test.go

--- a/src/pkg/net/http/header_test.go
+++ b/src/pkg/net/http/header_test.go
@@ -192,9 +192,12 @@ func BenchmarkHeaderWriteSubset(b *testing.B) {
 	}
 }
 
-func TestHeaderWriteSubsetMallocs(t *testing.T) {
+func TestHeaderWriteSubsetAllocs(t *testing.T) {
 	if testing.Short() {
-\t\tt.Skip("skipping malloc count in short mode")
+\t\tt.Skip("skipping alloc test in short mode")
+\t}
+\tif raceEnabled {
+\t\tt.Skip("skipping test under race detector")
 	}
 	if runtime.GOMAXPROCS(0) > 1 {
 		t.Skip("skipping; GOMAXPROCS>1")
@@ -204,6 +207,6 @@ func TestHeaderWriteSubsetMallocs(t *testing.T) {
 		testHeader.WriteSubset(&buf, nil)
 	})
 	if n > 0 {
-\t\tt.Errorf("mallocs = %g; want 0", n)
+\t\tt.Errorf("allocs = %g; want 0", n)
 	}
 }

src/pkg/net/http/race.go (新規追加)

--- /dev/null
+++ b/src/pkg/net/http/race.go
@@ -0,0 +1,11 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build race
+
+package http
+
+func init() {
+	raceEnabled = true
+}

コアとなるコードの解説

src/pkg/net/http/header.goraceEnabled 変数

var raceEnabled = false は、net/http パッケージ全体でRace Detectorが有効かどうかを判断するためのフラグとして機能します。この変数はパッケージレベルで定義されており、他のファイルからアクセス可能です。初期値は false で、Race Detectorが有効でない通常のビルドではこの値のままです。

src/pkg/net/http/race.goinit() 関数とビルドタグ

race.go ファイルは、Goのビルドシステムにおける「ビルドタグ」の典型的な使用例を示しています。 // +build race という行は、このファイルが go build -race のように -race フラグが指定された場合にのみコンパイルされることをGoツールチェインに指示します。 このファイル内の func init() { raceEnabled = true } は、パッケージが初期化される際に自動的に実行される関数です。したがって、Race Detectorが有効なビルドでは race.go がコンパイルされ、その init() 関数が実行されることで、header.go で定義された raceEnabled 変数が true に設定されます。これにより、net/http パッケージ内の他のコードがRace Detectorの有効/無効状態をプログラム的にチェックできるようになります。

src/pkg/net/http/header_test.goTestHeaderWriteSubsetAllocs

このテスト関数は、http.HeaderWriteSubset メソッドが、特定の条件下でメモリ割り当てを発生させないことを検証します。 testing.Short() は、go test -short フラグが指定された場合に true を返します。これは、実行時間の長いテストをスキップするための一般的な慣習です。 runtime.GOMAXPROCS(0) > 1 は、Goランタイムが複数のOSスレッドを利用できる場合に true を返します。これは、並行処理のテストにおいて特定の挙動を考慮する必要がある場合にテストをスキップする条件として使われることがあります。 そして、今回のコミットで追加された if raceEnabled { t.Skip("skipping test under race detector") } が最も重要な変更点です。前述の race.goraceEnabledtrue に設定されている場合(つまりRace Detectorが有効な場合)、このテストは「Race Detector下でのテストをスキップします」というメッセージと共にスキップされます。 b.ReportAllocs() はベンチマーク実行中のメモリ割り当てを報告するように設定し、n := testing.AllocsPerRun(100, func() { ... }) は指定された関数を100回実行し、その間の平均メモリ割り当て回数を n に格納します。このテストでは n が0であることを期待しており、もし0でなければエラーを報告します。

この一連の変更により、TestHeaderWriteSubsetAllocs は、Race Detectorが有効な環境では実行されなくなり、テストの安定性と信頼性が向上しました。

関連リンク

参考にした情報源リンク