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

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

このコミットは、Go言語の標準ライブラリ net パッケージ内の TestDialTimeout テストの信頼性を向上させるための変更です。具体的には、テストが不安定(deflake)になる問題を修正し、タイムアウト関連のテストがより確実に動作するように改善されています。

コミット

commit cdc642453bacfad6561eb4275bc55d752a9e92fb
Author: Albert Strasheim <fullung@gmail.com>
Date:   Thu Mar 14 09:42:29 2013 -0700

    net: deflake TestDialTimeout
    
    Fixes #3867.
    Fixes #3307.
    
    R=bradfitz, dvyukov
    CC=golang-dev
    https://golang.org/cl/7735044

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

https://github.com/golang/go/commit/cdc642453bacfad6561eb4275bc55d752a9e92fb

元コミット内容

net: deflake TestDialTimeout

このコミットの目的は、TestDialTimeout テストの不安定性(flakiness)を解消することです。具体的には、GoのIssue #3867 と #3307 を修正します。

変更の背景

TestDialTimeout は、ネットワーク接続のタイムアウト処理をテストするためのものです。しかし、このテストは実行環境やタイミングによって失敗することがあり、不安定な状態でした。このような不安定なテストは「flaky test」と呼ばれ、CI/CDパイプラインの信頼性を損ない、開発者が実際のバグとテストの不安定性を区別するのを困難にします。

コミットメッセージに記載されているIssue #3867と#3307は、このテストの不安定性に関連するものです。これらのIssueは、TestDialTimeoutが特定の条件下でタイムアウトを正しく検出できない、またはテストが意図しない挙動を示すことを報告していたと考えられます。

具体的な不安定性の原因としては、以下のようなものが考えられます。

  • バックログの競合: サーバー側のリスナーが受け入れられる接続のバックログサイズが小さすぎると、クライアントからの接続要求がすぐに拒否され、タイムアウトの挙動が不安定になる可能性があります。
  • テストのタイミング: ネットワーク操作は非同期であり、テストコードがネットワークイベントの発生を正確に予測できない場合、タイムアウトの検出が早すぎたり遅すぎたりして、テストが失敗することがあります。
  • リソースの枯渇: テスト中に多数の接続を試行する場合、一時的なポートの枯渇やOSのネットワークスタックの制限により、予期せぬエラーが発生する可能性があります。

このコミットは、これらの潜在的な問題に対処し、TestDialTimeoutがより堅牢で信頼性の高いテストとなることを目指しています。

前提知識の解説

Go言語のテスト

Go言語には、標準でテストフレームワークが組み込まれています。テストファイルは通常、テスト対象のファイルと同じディレクトリに _test.go というサフィックスを付けて配置されます。テスト関数は Test で始まり、*testing.T 型の引数を取ります。t.Fatal()t.Errorf() などのメソッドを使ってテストの失敗を報告します。

ネットワークプログラミングの基本

  • ソケット (Socket): ネットワーク通信のエンドポイント。IPアドレスとポート番号の組み合わせで識別されます。
  • リスナー (Listener): サーバー側で、特定のポートでクライアントからの接続要求を待ち受けるエンティティ。
  • バックログ (Backlog): リスナーが同時に受け入れ可能な、まだ Accept されていない接続要求のキューの最大サイズ。このサイズを超えると、新たな接続要求は拒否されることがあります。
  • ダイヤル (Dial): クライアント側で、特定のネットワークアドレス(IPアドレスとポート)に対して接続を確立しようとする操作。
  • タイムアウト (Timeout): ネットワーク操作(接続、読み書きなど)が指定された時間内に完了しない場合に、その操作を中断するメカニズム。

Flaky Test (不安定なテスト)

Flaky testとは、同じコードに対して同じテストを実行しても、成功したり失敗したりするテストのことです。これは、テストが外部要因(ネットワークの遅延、並行処理のタイミング、リソースの競合など)に依存している場合に発生しやすく、テスト結果の信頼性を著しく低下させます。Flaky testの修正は、CI/CDパイプラインの安定性と開発者の生産性向上に不可欠です。

技術的詳細

このコミットの主要な変更点は、TestDialTimeout テストにおけるリスナーのバックログサイズと、テストで試行する接続数の調整です。

  1. listenerBacklog の一時的な変更と復元: listenerBacklog は、Goの net パッケージ内部で使用される、リスナーのバックログサイズを決定する変数です。このコミットでは、テストの開始時に元の listenerBacklog の値を保存し、テスト終了時に defer ステートメントを使って元の値に戻すようにしています。これにより、テストが他のテストに影響を与えないようにしています。 最も重要な変更は、listenerBacklog = 1 と設定している点です。これは、リスナーが同時に受け入れられる接続のバックログサイズを意図的に非常に小さく(1に)設定することを意味します。

  2. numConns の増加: numConns は、テストで試行する接続の数を表します。変更前は listenerBacklog + 10 でしたが、変更後は listenerBacklog + 100 に増えています。listenerBacklog が1に設定されているため、これは実質的に 1 + 100 = 101 の接続を試行することを意味します。

これらの変更の組み合わせが、テストの不安定性を解消する鍵となります。

  • listenerBacklog = 1 の意図: バックログを1に設定することで、リスナーは同時に1つの接続しかキューに保持できません。これにより、多数の接続要求が同時に来た場合、ほとんどの接続要求はすぐに拒否されるか、タイムアウトする可能性が高まります。これは、タイムアウトの挙動をより確実にトリガーし、テストがその挙動を正確に検証できるようにするためのものです。もしバックログが大きすぎると、接続要求がキューに残り、タイムアウトが発生する前に受け入れられてしまう可能性があり、テストが意図したタイムアウトを検証できなくなることがあります。

  • numConns の増加の意図: 試行する接続数を10から100に増やすことで、テストはより多くの接続要求を同時に発行します。バックログが小さい(1)状態で多数の接続要求を送りつけることで、システムが接続を処理しきれなくなり、タイムアウトが発生するシナリオをより確実に再現できます。これにより、テストがタイムアウトの条件をより頻繁かつ確実に満たすようになり、不安定性が解消されると考えられます。

要するに、この変更は、意図的にネットワークの輻輳状態を作り出し、DialTimeout が期待通りに機能するかを厳密にテストするためのものです。

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

--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -28,12 +28,18 @@ func newLocalListener(t *testing.T) Listener {
 }
 
 func TestDialTimeout(t *testing.T) {
+	origBacklog := listenerBacklog
+	defer func() {
+		listenerBacklog = origBacklog
+	}()
+	listenerBacklog = 1
+
 	ln := newLocalListener(t)
 	defer ln.Close()
 
 	errc := make(chan error)
 
-	numConns := listenerBacklog + 10
+	numConns := listenerBacklog + 100
 
 	// TODO(bradfitz): It's hard to test this in a portable
 	// way. This is unfortunate, but works for now.

コアとなるコードの解説

  1. origBacklog := listenerBacklog: listenerBacklog の現在の値を origBacklog という変数に保存しています。これは、テストが終了した後に元の状態に戻すための準備です。

  2. defer func() { listenerBacklog = origBacklog }(): defer キーワードは、囲んでいる関数(この場合は TestDialTimeout)がリターンする直前に、指定された関数を実行することを保証します。ここでは、保存しておいた origBacklog の値を listenerBacklog に戻す無名関数を登録しています。これにより、このテストが listenerBacklog の値を変更しても、他のテストやシステム全体に影響を与えないようにしています。

  3. listenerBacklog = 1: listenerBacklog の値を 1 に設定しています。これは、前述の通り、リスナーが同時に受け入れられる接続のバックログサイズを意図的に非常に小さくすることで、タイムアウトのシナリオをより確実に発生させるための重要な変更です。

  4. numConns := listenerBacklog + 100: numConns の計算式が変更されています。以前は listenerBacklog + 10 でしたが、listenerBacklog + 100 になっています。listenerBacklog1 に設定されているため、numConns1 + 100 = 101 となります。これにより、テスト中に同時に試行される接続の数が大幅に増加し、バックログが小さい状態でのタイムアウト発生を促進します。

これらの変更により、TestDialTimeout は、ネットワークの輻輳状態をより確実にシミュレートし、DialTimeout 関数が期待通りにタイムアウトを処理できるかを検証できるようになります。

関連リンク

参考にした情報源リンク