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

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

このコミットは、Go言語の net パッケージにおける「Happy Eyeballs」アルゴリズムを用いたダイヤル処理において、ネストされたエラーが返される問題を修正し、未使用変数を削除するものです。これにより、エラーハンドリングが改善され、よりクリーンなエラー情報が提供されるようになります。

コミット

commit 7eb45d3c4a0956fc2207001360472ad048e544bc
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Thu Dec 19 10:00:15 2013 +0900

    net: don't return a nested error when happy eyeballs dialing
    
    Also removes an unused variable.
    
    Fixes #6795.
    
    R=adg, dave, bradfitz, gobot
    CC=golang-dev
    https://golang.org/cl/29440043

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

https://github.com/golang/go/commit/7eb45d3c4a0956fc2207001360472ad048e544bc

元コミット内容

このコミットは、Go言語の net パッケージにおいて、Happy Eyeballsダイヤル処理中にネストされたエラーが返されるのを防ぐことを目的としています。また、同時に未使用の変数を削除しています。この変更は、Issue #6795 を修正するものです。

変更の背景

Goの net パッケージには、IPv4とIPv6の両方のアドレスを持つホストに接続する際に、より迅速な接続確立を試みる「Happy Eyeballs」アルゴリズムが実装されています。このアルゴリズムは、複数の接続試行を並行して行い、最初に成功した接続を返します。しかし、これまでの実装では、すべての接続試行が失敗した場合に返されるエラーが、net.OpError の中にさらに別の net.OpError がネストされた形になっていました。

具体的には、dialMulti 関数(Happy Eyeballsのロジックを実装している部分)が、最終的なエラーを &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr} の形式で返していました。ここで lastErr は、個々の接続試行で発生した最後のエラーであり、これがすでに *net.OpError 型である場合がありました。その結果、エラーが *net.OpError の中に *net.OpError が含まれるという、ネストされた構造になってしまい、エラーハンドリングが複雑になる可能性がありました。

このコミットは、このようなネストされたエラー構造を解消し、よりシンプルで扱いやすいエラーを返すことを目的としています。また、この修正の一環として、エラー情報として使用されなくなった failAddr という未使用の変数も削除されています。

前提知識の解説

Happy Eyeballs (RFC 6555)

Happy Eyeballsは、デュアルスタック(IPv4とIPv6の両方に対応)のクライアントが、デュアルスタックのサーバーに接続する際に、接続確立の遅延を最小限に抑えるためのアルゴリズムです。IPv6ネットワークが利用可能であっても、IPv6接続が遅延したり失敗したりするケース(例えば、IPv6の経路が壊れている場合など)に対応するため、IPv4とIPv6の両方のアドレスへの接続をほぼ同時に開始し、先に成功した方を使用します。これにより、ユーザーは接続の遅延を感じにくくなります。

Go言語の net パッケージでは、DialDialContext などの関数が内部的にこのHappy Eyeballsアルゴリズムを利用して、最適な接続を確立しようとします。

Go言語の net パッケージと net.OpError

Go言語の net パッケージは、ネットワークI/Oのプリミティブを提供します。TCP/UDP接続、IPアドレスの解決、DNSルックアップなど、様々なネットワーク操作をサポートします。

net.OpError は、net パッケージで発生する一般的なネットワーク操作エラーを表す構造体です。これは、以下のようなフィールドを持ちます。

  • Op (string): 実行しようとした操作(例: "dial", "read", "write")
  • Net (string): ネットワークの種類(例: "tcp", "udp")
  • Addr (Addr): 関連するネットワークアドレス
  • Err (error): 根本的なエラー(syscall.Errnoos.SyscallError など)

OpError は、ネットワーク操作のどの段階で、どのような種類のエラーが発生したかを詳細に伝えるために設計されています。

racer 構造体と dialMulti 関数

dialMulti 関数は、Happy Eyeballsアルゴリズムの中核をなす部分で、複数のネットワークアドレスへの接続試行を並行して行います。この関数内で、各接続試行の結果を伝えるために racer という内部構造体が定義されています。

元の racer 構造体は以下のようになっていました。

type racer struct {
    Conn
    Addr
    error
}

ここで Conn は確立された接続、Addr は接続を試みたアドレス、error はその試行で発生したエラーを表します。dialMulti 関数は、これらの racer オブジェクトをチャネルを通じて受け取り、最初に成功した接続を返します。すべての試行が失敗した場合は、最後のエラーを返します。

技術的詳細

このコミットの主要な変更点は、dialMulti 関数がエラーを返す方法と、それに伴う racer 構造体の変更です。

ネストされた OpError の問題

前述の通り、dialMulti 関数は最終的に &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr} を返していました。ここで lastErr は、個々の dialSingle 呼び出しから返されるエラーであり、これもまた *net.OpError 型である可能性がありました。例えば、dialSingleconnection refused のようなエラーで失敗した場合、それは *net.OpError としてラップされて返されます。この *net.OpError がさらに dialMulti の中で別の *net.OpErrorErr フィールドに格納されると、エラーが二重にラップされることになります。

これは、エラーを処理する側から見ると不便です。エラーの根本原因にアクセスするためには、err.(*net.OpError).Err.(*net.OpError).Err のように、複数の型アサーションとフィールドアクセスが必要になる可能性があります。Goのエラーハンドリングの慣習では、エラーはできるだけシンプルに、そして根本原因に直接アクセスできるようにすることが望ましいとされています。

修正内容

このコミットでは、このネストされたエラーの問題を解決するために、以下の変更が行われました。

  1. racer 構造体からの Addr フィールドの削除: racer 構造体から Addr フィールドが削除されました。 変更前: type racer struct { Conn; Addr; error } 変更後: type racer struct { Conn; error } これは、最終的なエラーを返す際に failAddr 変数を使用しないようにするためです。failAddr は、最後に失敗したアドレスを保持していましたが、ネストされたエラーを避けるために、この情報が直接 OpErrorAddr フィールドに渡されるのではなく、lastErr が直接返されるようになったため、不要になりました。

  2. dialMulti の戻り値の変更: dialMulti 関数が、すべての接続試行が失敗した場合に返すエラーの形式が変更されました。 変更前: return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr} 変更後: return nil, lastErr

    この変更により、dialMulti は、個々の接続試行で発生した最後のエラー (lastErr) を直接返すようになります。lastErr がすでに *net.OpError であれば、それがそのまま返され、二重のネストが回避されます。もし lastErr*net.OpError でない場合(例えば、タイムアウトエラーなど)、そのエラーが直接返されます。これにより、エラーの構造がシンプルになり、エラーハンドリングが容易になります。

  3. failAddr 変数の削除: dialMulti 関数内で、failAddr という変数が宣言されていましたが、上記の変更により使用されなくなったため、削除されました。

これらの変更により、Happy Eyeballsダイヤル処理でエラーが発生した場合に、よりクリーンで直接的なエラー情報が提供されるようになります。

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

src/pkg/net/dial.go ファイルの dialMulti 関数に以下の変更が加えられました。

--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -172,7 +172,6 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
 func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) {
  	type racer struct {
  		Conn
- 		Addr
  		error
  	}
  	// Sig controls the flow of dial results on lane. It passes a
@@ -184,7 +183,7 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con
  		go func(ra Addr) {
  			c, err := dialSingle(net, addr, la, ra, deadline)
  			if _, ok := <-sig; ok {
- 				lane <- racer{c, ra, err}
+ 				lane <- racer{c, err}
  			} else if err == nil {
  				// We have to return the resources
  				// that belong to the other
@@ -195,12 +194,11 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con
  		}(ra.toAddr())\n 	}
  	defer close(sig)
- 	var failAddr Addr
  	lastErr := errTimeout
  	nracers := len(ras)
  	for nracers > 0 {
  		select {
  		case racer := <-lane:
  			if racer.error == nil {
  				return racer.Conn, nil
  			}
- 			failAddr = racer.Addr
  			lastErr = racer.error
  			nracers--
  		}
  	}
- 	return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr}
+ 	return nil, lastErr
 }\n \n // dialSingle attempts to establish and returns a single connection to

コアとなるコードの解説

  1. racer 構造体から Addr フィールドの削除:

    - 		Addr
    

    dialMulti 関数内で定義されている匿名構造体 racer から Addr フィールドが削除されました。これにより、各接続試行の結果をチャネルに送信する際に、アドレス情報を racer オブジェクトに含める必要がなくなりました。これは、最終的なエラーが OpError でラップされなくなったため、failAddr 変数が不要になったことと関連しています。

  2. lane チャネルへの racer オブジェクトの送信方法の変更:

    - 				lane <- racer{c, ra, err}
    + 				lane <- racer{c, err}
    

    dialSingle の結果を lane チャネルに送信する際、racer オブジェクトの初期化から ra (リモートアドレス) が取り除かれました。これは、racer 構造体から Addr フィールドが削除されたことによる直接的な変更です。

  3. failAddr 変数の削除:

    - 	var failAddr Addr
    

    dialMulti 関数内で宣言されていた failAddr 変数が削除されました。この変数は、最後に失敗した接続のアドレスを保持するために使用されていましたが、最終的なエラーの返し方が変更されたため、不要になりました。

  4. failAddr への代入の削除:

    - 			failAddr = racer.Addr
    

    lane チャネルから racer オブジェクトを受け取った際に、failAddr に値を代入する行が削除されました。これは、failAddr 変数自体が削除されたことによる変更です。

  5. dialMulti の戻り値の変更:

    - 	return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr}
    + 	return nil, lastErr
    

    これがこのコミットの最も重要な変更点です。dialMulti 関数が、すべての接続試行が失敗した場合に返すエラーの形式が変更されました。以前は、lastErrOpErrorErr フィールドにネストしていましたが、変更後は lastErr を直接返すようになりました。これにより、エラーのネストが解消され、エラーハンドリングがよりシンプルになります。

これらの変更により、Goの net パッケージは、Happy Eyeballsアルゴリズムを使用する際に、よりクリーンで扱いやすいエラーを返すようになり、開発者がエラーをデバッグしたり処理したりする際の負担が軽減されます。

関連リンク

参考にした情報源リンク