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

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

このドキュメントは、Go言語の標準ライブラリnetパッケージにおけるテストコードの修正に関するコミット(インデックス13098)について、その詳細な技術解説を提供します。具体的には、テストにおけるリソースリークの修正と、変数名の変更に焦点を当てています。

コミット

net: fix leak in test

このコミットは、Go言語のnetパッケージのテストコードにおけるリソースリークを修正し、同時にListener変数の名前をlからlnに変更しています。

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

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

元コミット内容

commit ba57c8800318d3378b438f5bf79ac276960c03e7
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat May 19 10:42:54 2012 +0900

    net: fix leak in test
    
    Also change the Listner variable name from l to ln.
    
    R=golang-dev, rsc, dave
    CC=golang-dev
    https://golang.org/cl/5918046

変更の背景

このコミットの主な背景は、netパッケージのテストコードnet_test.goにおいて、テスト実行後にネットワークリソース(特にListenerによって開かれたポート)が適切にクローズされず、リークが発生していた問題の修正です。リソースリークは、テストの信頼性を低下させ、特にCI/CD環境のような連続的なテスト実行において、ポートの枯渇やテストの不安定化を引き起こす可能性があります。

また、変数名lListenerの略として使われていましたが、より明確なln(Listenerの略)に変更することで、コードの可読性と保守性を向上させる意図があります。これはGo言語のコーディング規約において、短い変数名を使用する場合でも、その意味が明確であることが推奨される原則に沿ったものです。

前提知識の解説

Go言語のnetパッケージ

Go言語のnetパッケージは、ネットワークI/Oのプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルを扱うための機能が含まれています。

  • net.Listen(network, address string): 指定されたネットワーク(例: "tcp", "tcp4", "tcp6", "udp", "unix")とアドレスでネットワークリスナーを宣言します。成功するとnet.Listenerインターフェースを実装したオブジェクトを返します。
  • net.Dial(network, address string): 指定されたネットワークとアドレスに接続します。成功するとnet.Connインターフェースを実装したオブジェクトを返します。
  • net.Listenerインターフェース:
    • Accept() (Conn, error): 新しい接続を待ち受け、確立された接続をnet.Connとして返します。
    • Close() error: リスナーを閉じ、関連するネットワークリソースを解放します。
    • Addr() Addr: リスナーのネットワークアドレスを返します。
  • net.Connインターフェース:
    • Close() error: 接続を閉じます。
  • net.ListenPacket(network, address string): パケット指向のネットワーク接続(UDPなど)をリッスンします。成功するとnet.PacketConnインターフェースを実装したオブジェクトを返します。
  • net.PacketConnインターフェース:
    • ReadFrom(b []byte) (n int, addr Addr, err error): パケットを読み込みます。
    • Close() error: 接続を閉じます。

Go言語のテスト

Go言語のテストは、testingパッケージを使用して記述されます。テスト関数はTestXxx(*testing.T)というシグネチャを持ち、go testコマンドで実行されます。

  • t.Fatalf(format string, args ...interface{}): テストを失敗としてマークし、実行を停止します。
  • t.Errorf(format string, args ...interface{}): テストを失敗としてマークしますが、実行は継続します。
  • t.Logf(format string, args ...interface{}): テストのログに出力します。
  • deferステートメント: deferに続く関数呼び出しは、その関数がリターンする直前に実行されます。これはリソースのクリーンアップ(ファイルやネットワーク接続のクローズなど)に非常に便利です。

リソースリーク

プログラムが確保したメモリやファイルハンドル、ネットワークソケットなどのリソースを、使用後に適切に解放しない場合に発生する問題です。特にネットワークプログラミングでは、開いたソケットやリスナーを閉じ忘れると、ポートが占有され続け、新しい接続を受け付けられなくなったり、システムリソースを枯渇させたりする可能性があります。

技術的詳細

このコミットは、net_test.go内のTestShutdownTestTCPListenCloseTestUDPListenCloseという3つのテスト関数に影響を与えています。これらのテストは、ネットワークリスナーのライフサイクル(作成、接続の受け入れ、クローズ)を検証するものです。

リソースリークの修正

TestTCPListenClose関数において、l.Accept()がエラーを返した場合(リスナーがクローズされたためなど)、c.Close()が呼び出されないパスが存在していました。具体的には、Acceptがエラーを返した場合、if err == nilの条件が偽となり、c.Close()がスキップされていました。しかし、Acceptが成功してcが有効なnet.Connを返した場合、そのcがクローズされないままテスト関数が終了する可能性がありました。

修正前:

		_, err = l.Accept()
		if err == nil {
			t.Error("Accept succeeded")
		} else {
			t.Logf("Accept timeout error: %s (any error is fine)", err)
		}

修正後:

		c, err := ln.Accept()
		if err == nil {
			c.Close() // Acceptが成功した場合、接続をクローズする
			t.Error("Accept succeeded")
		} else {
			t.Logf("Accept timeout error: %s (any error is fine)", err)
		}

この変更により、Acceptが成功して接続が確立された場合でも、その接続が確実にクローズされるようになりました。これにより、テスト実行後のリソースリークが防止されます。

変数名の変更

net.Listenerおよびnet.PacketConnのインスタンスを指す変数名が、一貫してlからlnに変更されました。これは、Go言語の慣習に従い、より記述的で明確な変数名を使用するためのリファクタリングです。lは"listener"の略として一般的ですが、lnはより明確に"listener"を指し示します。この変更は機能的な影響はなく、コードの可読性向上のみを目的としています。

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

変更はsrc/pkg/net/net_test.goファイルに集中しています。

--- a/src/pkg/net/net_test.go
+++ b/src/pkg/net/net_test.go
@@ -16,15 +16,15 @@ func TestShutdown(t *testing.T) {
 		t.Logf("skipping test on %q", runtime.GOOS)
 		return
 	}
-	l, err := Listen("tcp", "127.0.0.1:0")
+	ln, err := Listen("tcp", "127.0.0.1:0")
 	if err != nil {
-		if l, err = Listen("tcp6", "[::1]:0"); err != nil {
+		if ln, err = Listen("tcp6", "[::1]:0"); err != nil {
 			t.Fatalf("ListenTCP on :0: %v", err)
 		}
 	}
 
 	go func() {
-		c, err := l.Accept()
+		c, err := ln.Accept()
 		if err != nil {
 			t.Fatalf("Accept: %v", err)
 		}
@@ -37,7 +37,7 @@ func TestShutdown(t *testing.T) {
 		c.Close()
 	}()
 
-	c, err := Dial("tcp", l.Addr().String())
+	c, err := Dial("tcp", ln.Addr().String())
 	if err != nil {
 		t.Fatalf("Dial: %v", err)
 	}
@@ -59,7 +59,7 @@ func TestShutdown(t *testing.T) {
 }
 
 func TestTCPListenClose(t *testing.T) {
-	l, err := Listen("tcp", "127.0.0.1:0")
+	ln, err := Listen("tcp", "127.0.0.1:0")
 	if err != nil {
 		t.Fatalf("Listen failed: %v", err)
 	}
@@ -67,11 +67,12 @@ func TestTCPListenClose(t *testing.T) {
 	done := make(chan bool, 1)
 	go func() {
 		time.Sleep(100 * time.Millisecond)
-		l.Close()
+		ln.Close()
 	}()
 	go func() {
-		_, err = l.Accept()
+		c, err := ln.Accept()
 		if err == nil {
+			c.Close() // 追加された行
 			t.Error("Accept succeeded")
 		} else {
 			t.Logf("Accept timeout error: %s (any error is fine)", err)
@@ -86,7 +87,7 @@ func TestTCPListenClose(t *testing.T) {
 }
 
 func TestUDPListenClose(t *testing.T) {
-	l, err := ListenPacket("udp", "127.0.0.1:0")
+	ln, err := ListenPacket("udp", "127.0.0.1:0")
 	if err != nil {
 		t.Fatalf("Listen failed: %v", err)
 	}
@@ -95,10 +96,10 @@ func TestUDPListenClose(t *testing.T) {
 	done := make(chan bool, 1)
 	go func() {
 		time.Sleep(100 * time.Millisecond)
-		l.Close()
+		ln.Close()
 	}()
 	go func() {
-		_, _, err = l.ReadFrom(buf)
+		_, _, err = ln.ReadFrom(buf)
 		if err == nil {
 			t.Error("ReadFrom succeeded")
 		} else {

コアとなるコードの解説

TestShutdown関数

この関数では、net.Listenで作成されたリスナー変数llnに変更されています。機能的な変更はありませんが、変数名の一貫性が保たれています。

TestTCPListenClose関数

この関数は、TCPリスナーがクローズされた後にAcceptがどのように振る舞うかをテストします。 最も重要な変更は以下の部分です。

 		c, err := ln.Accept()
 		if err == nil {
+			c.Close() // Acceptが成功した場合、接続をクローズする
 			t.Error("Accept succeeded")
 		} else {
 			t.Logf("Accept timeout error: %s (any error is fine)", err)
 		}

以前はAcceptが成功した場合(これはテストの意図に反するが、起こりうる)、取得したnet.Connオブジェクトcがクローズされずに残る可能性がありました。この修正により、Acceptが成功した場合にはc.Close()が明示的に呼び出され、リソースリークが防止されます。

TestUDPListenClose関数

この関数は、UDPパケットリスナーがクローズされた後にReadFromがどのように振る舞うかをテストします。 ここでも、リスナー変数llnに変更されています。機能的な変更はありません。

全体として、このコミットはGoのnetパッケージのテストの堅牢性を高め、リソース管理のベストプラクティスを適用しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(src/pkg/net/net_test.go
  • Gitコミット履歴
  • Go言語のコーディング規約に関する一般的な情報