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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージ内のテストファイルsrc/pkg/net/timeout_test.goに対する変更です。具体的には、TestDeadlineRaceというテスト関数の実行時間を短縮することを目的としています。

コミット

commit 9bbf1e1b725e12e207e6791da9d84f1f647df1d6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Aug 14 21:20:11 2013 +0400

    net: make TestDeadlineRace shorter
    1. Do less iterations in short mode
    2. Bound number of times SetDeadline is executed
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12937043

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

https://github.com/golang/go/commit/9bbf1e1b725e12e207e6791da9d84f1f647df1d6

元コミット内容

このコミットは、TestDeadlineRaceというテストの実行時間を短縮することを目的としています。具体的には、以下の2つの変更が行われました。

  1. testing.Short()モードの場合に、テストのイテレーション回数を減らす。
  2. SetDeadlineが実行される回数に上限を設ける。

変更の背景

TestDeadlineRaceは、ネットワーク接続のデッドライン(タイムアウト)設定と、そのデッドラインに達した際の読み取り操作が同時に行われる際の競合状態(レースコンディション)をテストするためのものです。このような競合状態のテストは、再現性が低く、多くのイテレーションを必要とすることが多いため、実行時間が長くなりがちです。

テストスイート全体の実行時間を最適化することは、開発サイクルにおいて非常に重要です。特に、継続的インテグレーション(CI)システムや開発者のローカル環境でのテスト実行が遅いと、フィードバックループが長くなり、生産性が低下します。このコミットは、テストの網羅性を維持しつつ、実行時間を短縮することで、開発効率の向上に貢献することを目的としています。

前提知識の解説

Go言語のnetパッケージとデッドライン

Go言語のnetパッケージは、ネットワークI/Oのプリミティブを提供します。TCP/UDP接続やUnixドメインソケットなど、様々なネットワーク通信を扱うことができます。 net.Connインターフェースは、ネットワーク接続を表し、以下のメソッドを含みます。

  • Read(b []byte) (n int, err error): データを読み取ります。
  • Write(b []byte) (n int, err error): データを書き込みます。
  • SetDeadline(t time.Time) error: 読み取りと書き込みの両方に適用される絶対的なタイムアウトを設定します。指定された時刻までにI/O操作が完了しない場合、操作はタイムアウトエラーを返します。
  • SetReadDeadline(t time.Time) error: 読み取り操作のみに適用される絶対的なタイムアウトを設定します。
  • SetWriteDeadline(t time.Time) error: 書き込み操作のみに適用される絶対的なタイムアウトを設定します。

これらのデッドライン設定は、ネットワークの応答がない場合や、悪意のあるピアからの攻撃などによってI/O操作がブロックされ続けることを防ぐために重要です。

レースコンディション(競合状態)

レースコンディションとは、複数のゴルーチン(またはスレッド)が共有リソースに同時にアクセスし、そのアクセス順序によって結果が変わってしまうような状況を指します。ネットワーク接続のデッドライン設定は、通常、接続オブジェクトの内部状態を変更するため、複数のゴルーチンが同時にSetDeadlineを呼び出したり、SetDeadlineRead/Writeを同時に呼び出したりすると、予期せぬ動作やバグ(例えば、デッドラインが正しく適用されない、I/O操作が誤ってタイムアウトする/しないなど)が発生する可能性があります。TestDeadlineRaceは、このような潜在的な問題を検出するために設計されたテストです。

testing.Short()

Go言語の標準テストパッケージtestingには、Short()という関数があります。これは、go test -shortコマンドでテストを実行した場合にtrueを返します。この機能は、開発者がテストスイート全体を素早く実行したい場合に、時間のかかるテスト(例えば、ネットワークI/Oを伴うテスト、大量の計算を行うテスト、外部リソースに依存するテストなど)の一部をスキップしたり、イテレーション回数を減らしたりするために使用されます。これにより、CI/CDパイプラインの高速化や、ローカル開発時の生産性向上が図られます。

技術的詳細

このコミットは、TestDeadlineRaceテストの実行ロジックを変更することで、その実行時間を短縮しています。

変更前は、SetDeadlineを呼び出すゴルーチンは無限ループfor {}で実行され、c.Read(buf[:])を呼び出すメインのゴルーチンは1024回イテレーションしていました。これは、競合状態を十分に引き起こすために多くの試行回数を必要とするためですが、テストの実行時間が長くなる原因でもありました。

変更後は、Nという変数が導入されました。このNはデフォルトで1000に設定されていますが、testing.Short()true(つまり、go test -shortで実行された場合)の際には50に設定されます。

  1. SetDeadlineの実行回数の制限: SetDeadlineを呼び出すゴルーチン内の無限ループfor {}が、for i := 0; i < N; i++に変更されました。これにより、SetDeadlineの呼び出し回数がN回に制限されます。go test -shortモードでは、この回数が50回に大幅に削減されます。
  2. Read操作のイテレーション回数の制限: メインのゴルーチンで行われるc.Read(buf[:])のループも、for i := 0; i < 1024; i++からfor i := 0; i < N; i++に変更されました。これにより、Read操作のイテレーション回数もN回に制限され、go test -shortモードでは50回になります。

これらの変更により、go test -shortコマンドで実行された場合、TestDeadlineRaceは大幅に少ないイテレーションで完了し、テストスイート全体の実行時間が短縮されます。通常のテスト実行(go test)では、Nは1000のままであり、十分なイテレーションが確保されるため、テストの網羅性は維持されます。

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

変更はsrc/pkg/net/timeout_test.goファイル内のTestDeadlineRace関数に集中しています。

--- a/src/pkg/net/timeout_test.go
+++ b/src/pkg/net/timeout_test.go
@@ -710,6 +710,10 @@ func TestDeadlineRace(t *testing.T) {
 		t.Skipf("skipping test on %q", runtime.GOOS)
 	}
 
+	N := 1000
+	if testing.Short() {
+		N = 50
+	}
 	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
 	ln := newLocalListener(t)
 	defer ln.Close()
@@ -721,7 +725,7 @@ func TestDeadlineRace(t *testing.T) {
 	done := make(chan bool)
 	go func() {
 		t := time.NewTicker(2 * time.Microsecond).C
-		for {
+		for i := 0; i < N; i++ {
 			if err := c.SetDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
 				break
 			}
@@ -730,7 +734,7 @@ func TestDeadlineRace(t *testing.T) {
 		done <- true
 	}()
 	var buf [1]byte
-	for i := 0; i < 1024; i++ {
+	for i := 0; i < N; i++ {
 		c.Read(buf[:]) // ignore possible timeout errors
 	}
 	c.Close()

コアとなるコードの解説

  1. N変数の導入とtesting.Short()による値の変更:

    	N := 1000
    	if testing.Short() {
    		N = 50
    	}
    

    この部分で、テストのイテレーション回数を制御するN変数が定義されます。デフォルト値は1000ですが、go test -shortオプションが指定されている場合は50に上書きされます。これにより、短いテスト実行モードでのイテレーション回数が大幅に削減されます。

  2. SetDeadline呼び出しループの変更:

    		// 変更前: for {
    		for i := 0; i < N; i++ {
    			if err := c.SetDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
    				break
    			}
    			<-t // Tickerからの信号を待つ
    		}
    

    SetDeadlineを繰り返し呼び出すゴルーチン内のループが、無限ループfor {}からfor i := 0; i < N; i++に変更されました。これにより、SetDeadlineの呼び出し回数がN回に制限され、特にshortモードでのテスト実行が高速化されます。

  3. Read操作ループの変更:

    	// 変更前: for i := 0; i < 1024; i++ {
    	for i := 0; i < N; i++ {
    		c.Read(buf[:]) // ignore possible timeout errors
    	}
    

    メインのゴルーチンでc.Readを繰り返し呼び出すループも、固定回数1024からN回に変更されました。これも同様に、shortモードでのテスト実行時間を短縮する効果があります。c.Readはタイムアウトエラーを無視するようにコメントされており、これは競合状態のテストにおいて、タイムアウトが発生してもテストが中断しないようにするためです。

これらの変更により、TestDeadlineRaceは、通常のテスト実行では十分なイテレーションを確保しつつ、go test -shortモードでは迅速に完了するようになり、テストスイート全体の効率が向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(src/pkg/net/timeout_test.go
  • Go言語のテストに関する一般的なプラクティスとtesting.Short()の利用例に関する情報(一般的なGoプログラミングの知識に基づく)
  • Go言語の競合状態とテストに関する情報(一般的なGoプログラミングの知識に基づく)
  • https://golang.org/cl/12937043 (Gerrit Code Review)