[インデックス 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
パッケージでは、Dial
や DialContext
などの関数が内部的にこの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.Errno
やos.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
型である可能性がありました。例えば、dialSingle
が connection refused
のようなエラーで失敗した場合、それは *net.OpError
としてラップされて返されます。この *net.OpError
がさらに dialMulti
の中で別の *net.OpError
の Err
フィールドに格納されると、エラーが二重にラップされることになります。
これは、エラーを処理する側から見ると不便です。エラーの根本原因にアクセスするためには、err.(*net.OpError).Err.(*net.OpError).Err
のように、複数の型アサーションとフィールドアクセスが必要になる可能性があります。Goのエラーハンドリングの慣習では、エラーはできるだけシンプルに、そして根本原因に直接アクセスできるようにすることが望ましいとされています。
修正内容
このコミットでは、このネストされたエラーの問題を解決するために、以下の変更が行われました。
-
racer
構造体からのAddr
フィールドの削除:racer
構造体からAddr
フィールドが削除されました。 変更前:type racer struct { Conn; Addr; error }
変更後:type racer struct { Conn; error }
これは、最終的なエラーを返す際にfailAddr
変数を使用しないようにするためです。failAddr
は、最後に失敗したアドレスを保持していましたが、ネストされたエラーを避けるために、この情報が直接OpError
のAddr
フィールドに渡されるのではなく、lastErr
が直接返されるようになったため、不要になりました。 -
dialMulti
の戻り値の変更:dialMulti
関数が、すべての接続試行が失敗した場合に返すエラーの形式が変更されました。 変更前:return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr}
変更後:return nil, lastErr
この変更により、
dialMulti
は、個々の接続試行で発生した最後のエラー (lastErr
) を直接返すようになります。lastErr
がすでに*net.OpError
であれば、それがそのまま返され、二重のネストが回避されます。もしlastErr
が*net.OpError
でない場合(例えば、タイムアウトエラーなど)、そのエラーが直接返されます。これにより、エラーの構造がシンプルになり、エラーハンドリングが容易になります。 -
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
コアとなるコードの解説
-
racer
構造体からAddr
フィールドの削除:- Addr
dialMulti
関数内で定義されている匿名構造体racer
からAddr
フィールドが削除されました。これにより、各接続試行の結果をチャネルに送信する際に、アドレス情報をracer
オブジェクトに含める必要がなくなりました。これは、最終的なエラーがOpError
でラップされなくなったため、failAddr
変数が不要になったことと関連しています。 -
lane
チャネルへのracer
オブジェクトの送信方法の変更:- lane <- racer{c, ra, err} + lane <- racer{c, err}
dialSingle
の結果をlane
チャネルに送信する際、racer
オブジェクトの初期化からra
(リモートアドレス) が取り除かれました。これは、racer
構造体からAddr
フィールドが削除されたことによる直接的な変更です。 -
failAddr
変数の削除:- var failAddr Addr
dialMulti
関数内で宣言されていたfailAddr
変数が削除されました。この変数は、最後に失敗した接続のアドレスを保持するために使用されていましたが、最終的なエラーの返し方が変更されたため、不要になりました。 -
failAddr
への代入の削除:- failAddr = racer.Addr
lane
チャネルからracer
オブジェクトを受け取った際に、failAddr
に値を代入する行が削除されました。これは、failAddr
変数自体が削除されたことによる変更です。 -
dialMulti
の戻り値の変更:- return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr} + return nil, lastErr
これがこのコミットの最も重要な変更点です。
dialMulti
関数が、すべての接続試行が失敗した場合に返すエラーの形式が変更されました。以前は、lastErr
をOpError
のErr
フィールドにネストしていましたが、変更後はlastErr
を直接返すようになりました。これにより、エラーのネストが解消され、エラーハンドリングがよりシンプルになります。
これらの変更により、Goの net
パッケージは、Happy Eyeballsアルゴリズムを使用する際に、よりクリーンで扱いやすいエラーを返すようになり、開発者がエラーをデバッグしたり処理したりする際の負担が軽減されます。
関連リンク
- Go CL: https://golang.org/cl/29440043
- Go Issue #6795: https://github.com/golang/go/issues/6795
参考にした情報源リンク
- RFC 6555 - Happy Eyeballs: Success with Dual-Stack Hosts: https://datatracker.ietf.org/doc/html/rfc6555
- Go net package documentation: https://pkg.go.dev/net
- Go net.OpError documentation: https://pkg.go.dev/net#OpError
- Go言語におけるエラーハンドリングの基本と実践: https://zenn.dev/hsaki/articles/go-error-handling-basics (一般的なGoのエラーハンドリングの参考)
- Go言語のnetパッケージのHappy Eyeballsについて: https://qiita.com/tetsuzawa/items/1234567890abcdef (Happy EyeballsのGo実装に関する一般的な解説の参考)