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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のunix_test.goファイルに対する修正です。具体的には、Unixドメインソケットのテストコードにおける潜在的なバグを修正しています。

コミット

commit 6ab49fbc6e974643826f6e795ab6a7279d0991eb
Author: ChaiShushan <chaishushan@gmail.com>
Date:   Mon Aug 5 11:59:59 2013 +1000

    net: fix some test bug
    
    Fixes #5785.
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/10587043

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

https://github.com/golang/go/commit/6ab49fbc6e974643826f6e795ab6a7279d0991eb

元コミット内容

net: fix some test bug

このコミットは、netパッケージ内のいくつかのテストバグを修正することを目的としています。具体的には、unix_test.goファイルにおけるTestUnixConnLocalAndRemoteNamesTestUnixgramConnLocalAndRemoteNamesテスト関数内で、ループ変数laddrが意図せず共有されてしまう問題を解決しています。

変更の背景

このコミットは、Go Issue 5785 (Fixes #5785) に対応するものです。元のコードでは、for ... rangeループ内でladdr変数が再利用されていました。Go言語のfor ... rangeループでは、イテレーションごとに新しい変数が作成されるのではなく、ループ変数が再利用されます。このため、クロージャやゴルーチン内でループ変数をキャプチャする際に、意図しない値が参照されてしまうというバグが発生する可能性があります。

この特定のケースでは、テスト関数内でladdrがループ内で定義され、その後の処理で参照される際に、ループの最終イテレーションの値が使われてしまう可能性がありました。これにより、テストが不安定になったり、誤った結果を報告したりする可能性がありました。

前提知識の解説

Go言語のfor ... rangeループと変数のスコープ

Go言語のfor ... rangeループは、コレクション(配列、スライス、マップ、文字列、チャネル)をイテレートするための構文です。このループの重要な特性の一つは、イテレーション変数のスコープと再利用です。

for index, value := range collection {
    // index と value はループの各イテレーションで再利用される
}

上記の例では、indexvalueはループの各イテレーションで新しい値が割り当てられますが、それらの変数はループ全体で同じメモリ位置を共有します。これは、ループ内でゴルーチンを起動したり、クロージャを作成したりする場合に問題となることがあります。

例えば、以下のようなコードを考えます。

package main

import (
	"fmt"
	"time"
)

func main() {
	nums := []int{1, 2, 3}
	for _, n := range nums {
		go func() {
			fmt.Println(n) // 意図しない動作をする可能性がある
		}()
	}
	time.Sleep(time.Second)
}

このコードを実行すると、期待通りに1, 2, 3が出力されるとは限りません。多くの場合、3, 3, 3のように、ループの最後の値が複数回出力されることがあります。これは、ゴルーチンが実行される時点でnがすでにループの最終値に更新されているためです。

この問題を解決するためには、ループ変数をゴルーチンやクロージャの引数として渡すか、新しい変数にコピーする必要があります。

package main

import (
	"fmt"
	"time"
)

func main() {
	nums := []int{1, 2, 3}
	for _, n := range nums {
		num := n // 新しい変数にコピー
		go func() {
			fmt.Println(num)
		}()
	}
	time.Sleep(time.Second)
}

この修正により、各ゴルーチンはループの各イテレーションにおけるnの値を正しくキャプチャし、期待通りの出力が得られます。

Unixドメインソケット

Unixドメインソケット(Unix Domain Sockets, UDS)は、同じホスト上のプロセス間通信(IPC)のためのメカニズムです。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、Unixドメインソケットはファイルシステム上のパス名(ソケットファイル)を介して通信を行います。

主な特徴は以下の通りです。

  • 高速性: ネットワークスタックを介さないため、TCP/IPソケットよりも高速な通信が可能です。
  • セキュリティ: ファイルシステム上のパーミッションによってアクセス制御が可能です。
  • 信頼性: ストリームソケット(SOCK_STREAM)はTCPと同様に信頼性の高い接続を提供し、データグラムソケット(SOCK_DGRAM)はUDPと同様にコネクションレスな通信を提供します。

Go言語のnetパッケージは、Unixドメインソケットを含む様々なネットワークプロトコルをサポートしています。net.ResolveUnixAddrnet.ListenUnixnet.DialUnixなどの関数が提供されています。

技術的詳細

このコミットの技術的詳細は、Go言語のfor ... rangeループにおける変数の再利用問題に起因するものです。

src/pkg/net/unix_test.go内のTestUnixConnLocalAndRemoteNamesおよびTestUnixgramConnLocalAndRemoteNames関数では、以下のようなループ構造がありました。

for _, laddr := range []string{"", testUnixAddr()} {
    // ... laddr を使用した処理 ...
}

このループ内で、laddrは各イテレーションで新しい値(空文字列またはtestUnixAddr()の結果)が割り当てられます。しかし、laddr変数はループ全体で同じメモリ位置を共有しています。もしこのループ内でladdrをキャプチャするようなクロージャやゴルーチンが作成されていた場合、それらが実行されるタイミングによっては、laddrがループの最終値に更新されてしまっている可能性がありました。

このコミットでは、この問題を解決するために、ループ内でladdrの値を新しいローカル変数にコピーしています。

for _, laddr := range []string{"", testUnixAddr()} {
    laddr := laddr // ここで新しいローカル変数 'laddr' を宣言し、ループ変数の値をコピー
    // ... 新しいローカル変数 laddr を使用した処理 ...
}

このladdr := laddrという行は、シャドーイング(shadowing)と呼ばれるテクニックです。これは、外側のスコープで定義された変数と同じ名前の新しい変数を内側のスコープで宣言し、外側の変数を「隠す」ことを意味します。この場合、ループの各イテレーションで、そのイテレーションのladdrの値を保持する新しいladdr変数が作成されます。これにより、その後のコードブロックでladdrを参照する際に、常にそのイテレーションの正しい値が使用されることが保証されます。

この修正は、特にテストコードにおいて重要です。テストは再現性と信頼性が求められるため、このような非決定的なバグの原因となる可能性のあるコードは修正されるべきです。

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

変更はsrc/pkg/net/unix_test.goファイルにのみ行われています。

--- a/src/pkg/net/unix_test.go
+++ b/src/pkg/net/unix_test.go
@@ -141,6 +141,7 @@ func TestUnixAutobind(t *testing.T) {
 
 func TestUnixConnLocalAndRemoteNames(t *testing.T) {
 	for _, laddr := range []string{"", testUnixAddr()} {
+		laddr := laddr
 		taddr := testUnixAddr()
 		ta, err := ResolveUnixAddr("unix", taddr)
 		if err != nil {
@@ -196,6 +197,7 @@ func TestUnixConnLocalAndRemoteNames(t *testing.T) {
 
 func TestUnixgramConnLocalAndRemoteNames(t *testing.T) {
 	for _, laddr := range []string{"", testUnixAddr()} {
+		laddr := laddr
 		taddr := testUnixAddr()
 		ta, err := ResolveUnixAddr("unixgram", taddr)
 		if err != nil {

コアとなるコードの解説

追加されたladdr := laddrという行がこのコミットの核心です。

  • TestUnixConnLocalAndRemoteNames関数:

    • このテスト関数は、Unixドメインソケット接続のローカルアドレスとリモートアドレスの名前解決が正しく行われることを検証します。
    • for _, laddr := range []string{"", testUnixAddr()}ループは、ローカルアドレスとして空文字列(自動バインド)と特定のUnixソケットアドレスの両方をテストするために使用されます。
    • 追加されたladdr := laddrは、ループの各イテレーションで、そのイテレーションのladdrの値を新しいローカル変数laddrにコピーします。これにより、ループ内でladdrが参照される際に、常にそのイテレーションの正しい値が使用されることが保証され、テストの信頼性が向上します。
  • TestUnixgramConnLocalAndRemoteNames関数:

    • このテスト関数は、Unixドメインデータグラムソケット接続のローカルアドレスとリモートアドレスの名前解決が正しく行われることを検証します。
    • TestUnixConnLocalAndRemoteNamesと同様に、for ... rangeループ内でladdr変数のシャドーイングが行われ、同様のバグが修正されています。

この修正は、Go言語のfor ... rangeループのセマンティクスを理解し、特にクロージャやゴルーチン内でループ変数をキャプチャする際の一般的な落とし穴を回避するための典型的なパターンを示しています。テストコードにおいては、このような細かな修正がテストの安定性と正確性を保つ上で非常に重要です。

関連リンク

参考にした情報源リンク

  • Go言語のfor ... rangeループとクロージャに関する一般的な情報源(例: Go言語の公式ドキュメント、Go言語のブログ記事、Stack Overflowなど)
  • Unixドメインソケットに関する一般的な情報源(例: Linux manページ、ネットワークプログラミングの書籍など)
  • Go言語のnetパッケージのドキュメント: https://pkg.go.dev/net
  • Go言語のテストに関するドキュメント: https://pkg.go.dev/testing

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のunix_test.goファイルに対する修正です。具体的には、Unixドメインソケットのテストコードにおける潜在的なバグを修正しています。

コミット

commit 6ab49fbc6e974643826f6e795ab6a7279d0991eb
Author: ChaiShushan <chaishushan@gmail.com>
Date:   Mon Aug 5 11:59:59 2013 +1000

    net: fix some test bug
    
    Fixes #5785.
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/10587043

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

https://github.com/golang/go/commit/6ab49fbc6e974643826f6e795ab6a7279d0991eb

元コミット内容

net: fix some test bug

このコミットは、netパッケージ内のいくつかのテストバグを修正することを目的としています。具体的には、unix_test.goファイルにおけるTestUnixConnLocalAndRemoteNamesTestUnixgramConnLocalAndRemoteNamesテスト関数内で、ループ変数laddrが意図せず共有されてしまう問題を解決しています。

変更の背景

このコミットは、Go Issue 5785 (Fixes #5785) に対応するものです。元のコードでは、for ... rangeループ内でladdr変数が再利用されていました。Go言語のfor ... rangeループでは、イテレーションごとに新しい変数が作成されるのではなく、ループ変数が再利用されます。このため、クロージャやゴルーチン内でループ変数をキャプチャする際に、意図しない値が参照されてしまうというバグが発生する可能性があります。

この特定のケースでは、テスト関数内でladdrがループ内で定義され、その後の処理で参照される際に、ループの最終イテレーションの値が使われてしまう可能性がありました。これにより、テストが不安定になったり、誤った結果を報告したりする可能性がありました。

前提知識の解説

Go言語のfor ... rangeループと変数のスコープ

Go言語のfor ... rangeループは、コレクション(配列、スライス、マップ、文字列、チャネル)をイテレートするための構文です。このループの重要な特性の一つは、イテレーション変数のスコープと再利用です。

for index, value := range collection {
    // index と value はループの各イテレーションで再利用される
}

上記の例では、indexvalueはループの各イテレーションで新しい値が割り当てられますが、それらの変数はループ全体で同じメモリ位置を共有します。もし、このループ内でゴルーチンを起動したり、クロージャを作成したりして、これらのループ変数を参照した場合、ゴルーチンやクロージャが実際に実行される時点では、ループ変数の値はループの最終イテレーションの値になっている可能性があります。これは「変数キャプチャ」の問題として知られています。

例えば、以下のようなコードを考えます。

package main

import (
	"fmt"
	"time"
)

func main() {
	nums := []int{1, 2, 3}
	for _, n := range nums {
		go func() {
			fmt.Println(n) // 意図しない動作をする可能性がある
		}()
	}
	time.Sleep(time.Second)
}

このコードを実行すると、期待通りに1, 2, 3が出力されるとは限りません。多くの場合、3, 3, 3のように、ループの最後の値が複数回出力されることがあります。これは、ゴルーチンが実行される時点でnがすでにループの最終値に更新されているためです。

この問題を解決するためには、ループ変数をゴルーチンやクロージャの引数として渡すか、新しい変数にコピーする必要があります。

package main

import (
	"fmt"
	"time"
)

func main() {
	nums := []int{1, 2, 3}
	for _, n := range nums {
		num := n // 新しい変数にコピー
		go func() {
			fmt.Println(num)
		}()
	}
	time.Sleep(time.Second)
}

この修正により、各ゴルーチンはループの各イテレーションにおけるnの値を正しくキャプチャし、期待通りの出力が得られます。

Unixドメインソケット

Unixドメインソケット(Unix Domain Sockets, UDS)は、同じホスト上のプロセス間通信(IPC)のためのメカニズムです。TCP/IPソケットがネットワークを介した通信に使用されるのに対し、Unixドメインソケットはファイルシステム上のパス名(ソケットファイル)を介して通信を行います。

主な特徴は以下の通りです。

  • 高速性: ネットワークスタックを介さないため、TCP/IPソケットよりも高速な通信が可能です。
  • セキュリティ: ファイルシステム上のパーミッションによってアクセス制御が可能です。
  • 信頼性: ストリームソケット(SOCK_STREAM)はTCPと同様に信頼性の高い接続を提供し、データグラムソケット(SOCK_DGRAM)はUDPと同様にコネクションレスな通信を提供します。

Go言語のnetパッケージは、Unixドメインソケットを含む様々なネットワークプロトコルをサポートしています。net.ResolveUnixAddrnet.ListenUnixnet.DialUnixなどの関数が提供されています。

技術的詳細

このコミットの技術的詳細は、Go言語のfor ... rangeループにおける変数の再利用問題に起因するものです。

src/pkg/net/unix_test.go内のTestUnixConnLocalAndRemoteNamesおよびTestUnixgramConnLocalAndRemoteNames関数では、以下のようなループ構造がありました。

for _, laddr := range []string{"", testUnixAddr()} {
    // ... laddr を使用した処理 ...
}

このループ内で、laddrは各イテレーションで新しい値(空文字列またはtestUnixAddr()の結果)が割り当てられます。しかし、laddr変数はループ全体で同じメモリ位置を共有しています。もしこのループ内でladdrをキャプチャするようなクロージャやゴルーチンが作成されていた場合、それらが実行されるタイミングによっては、laddrがループの最終値に更新されてしまっている可能性がありました。

このコミットでは、この問題を解決するために、ループ内でladdrの値を新しいローカル変数にコピーしています。

for _, laddr := range []string{"", testUnixAddr()} {
    laddr := laddr // ここで新しいローカル変数 'laddr' を宣言し、ループ変数の値をコピー
    // ... 新しいローカル変数 laddr を使用した処理 ...
}

このladdr := laddrという行は、シャドーイング(shadowing)と呼ばれるテクニックです。これは、外側のスコープで定義された変数と同じ名前の新しい変数を内側のスコープで宣言し、外側の変数を「隠す」ことを意味します。この場合、ループの各イテレーションで、そのイテレーションのladdrの値を保持する新しいladdr変数が作成されます。これにより、その後のコードブロックでladdrを参照する際に、常にそのイテレーションの正しい値が使用されることが保証されます。

この修正は、特にテストコードにおいて重要です。テストは再現性と信頼性が求められるため、このような非決定的なバグの原因となる可能性のあるコードは修正されるべきです。

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

変更はsrc/pkg/net/unix_test.goファイルにのみ行われています。

--- a/src/pkg/net/unix_test.go
+++ b/src/pkg/net/unix_test.go
@@ -141,6 +141,7 @@ func TestUnixAutobind(t *testing.T) {
 
 func TestUnixConnLocalAndRemoteNames(t *testing.T) {
 	for _, laddr := range []string{"", testUnixAddr()} {
+		laddr := laddr
 		taddr := testUnixAddr()
 		ta, err := ResolveUnixAddr("unix", taddr)
 		if err != nil {
@@ -196,6 +197,7 @@ func TestUnixConnLocalAndRemoteNames(t *testing.T) {
 
 func TestUnixgramConnLocalAndRemoteNames(t *testing.T) {
 	for _, laddr := range []string{"", testUnixAddr()} {
+		laddr := laddr
 		taddr := testUnixAddr()
 		ta, err := ResolveUnixAddr("unixgram", taddr)
 		if err != nil {

コアとなるコードの解説

追加されたladdr := laddrという行がこのコミットの核心です。

  • TestUnixConnLocalAndRemoteNames関数:

    • このテスト関数は、Unixドメインソケット接続のローカルアドレスとリモートアドレスの名前解決が正しく行われることを検証します。
    • for _, laddr := range []string{"", testUnixAddr()}ループは、ローカルアドレスとして空文字列(自動バインド)と特定のUnixソケットアドレスの両方をテストするために使用されます。
    • 追加されたladdr := laddrは、ループの各イテレーションで、そのイテレーションのladdrの値を新しいローカル変数laddrにコピーします。これにより、ループ内でladdrが参照される際に、常にそのイテレーションの正しい値が使用されることが保証され、テストの信頼性が向上します。
  • TestUnixgramConnLocalAndRemoteNames関数:

    • このテスト関数は、Unixドメインデータグラムソケット接続のローカルアドレスとリモートアドレスの名前解決が正しく行われることを検証します。
    • TestUnixConnLocalAndRemoteNamesと同様に、for ... rangeループ内でladdr変数のシャドーイングが行われ、同様のバグが修正されています。

この修正は、Go言語のfor ... rangeループのセマンティクスを理解し、特にクロージャやゴルーチン内でループ変数をキャプチャする際の一般的な落とし穴を回避するための典型的なパターンを示しています。テストコードにおいては、このような細かな修正がテストの安定性と正確性を保つ上で非常に重要です。

関連リンク

参考にした情報源リンク

  • Go言語のfor ... rangeループとクロージャに関する一般的な情報源(例: Go言語の公式ドキュメント、Go言語のブログ記事、Stack Overflowなど)
  • Unixドメインソケットに関する一般的な情報源(例: Linux manページ、ネットワークプログラミングの書籍など)
  • Go言語のnetパッケージのドキュメント: https://pkg.go.dev/net
  • Go言語のテストに関するドキュメント: https://pkg.go.dev/testing