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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおける、プロトコル固有のDialおよびListen関数が返すエラー値の一貫性を向上させるための変更です。具体的には、これらの関数が返すエラーが、net.OpError型でラップされるように修正されています。

コミット

commit e4bb139e75bf9bff76ebe6e060b7196579ce2172
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Wed Aug 28 19:51:02 2013 +0900

    net: make protocol-specific Dial and Listen return consistent error value
    
    Update #4856
    
    R=golang-dev, bradfitz, dave
    CC=golang-dev
    https://golang.org/cl/12916046

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

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

元コミット内容

net: make protocol-specific Dial and Listen return consistent error value

Update #4856

R=golang-dev, bradfitz, dave
CC=golang-dev
https://golang.org/cl/12916046

変更の背景

このコミットは、Go言語のnetパッケージにおけるエラーハンドリングの一貫性の問題を解決するために行われました。Goのnetパッケージでは、ネットワーク操作(ダイヤル、リスンなど)中に発生する様々なエラーを扱うためにnet.OpErrorという構造体が定義されています。しかし、プロトコル固有のDialListen関数(例: DialIP, ListenIP, DialTCP, ListenTCPなど)が、常にnet.OpErrorを返しているわけではありませんでした。

具体的には、ネットワークタイプが不明な場合や、アドレスが欠落している場合など、一部のエラーケースでは直接UnknownNetworkErrorerrMissingAddressといった基底のエラー型が返されていました。これにより、呼び出し元がエラーの種類を判別する際に、net.OpErrorの型アサーションを行うだけでは不十分であり、複数のエラー型を考慮する必要がありました。

Issue #4856("net: make protocol-specific Dial and Listen return consistent error value")で報告されたこの問題は、ユーザーがネットワークエラーを統一的に処理することを困難にしていました。このコミットは、すべてのネットワーク操作関連のエラーをnet.OpErrorでラップすることで、エラーハンドリングの予測可能性と堅牢性を向上させることを目的としています。

前提知識の解説

Go言語のエラーハンドリング

Go言語では、エラーは多値返却の2番目の戻り値としてerrorインターフェース型で返されるのが一般的です。errorインターフェースは、Error() stringメソッドを持つ任意の型によって実装できます。

type error interface {
    Error() string
}

net.OpError

netパッケージでは、ネットワーク操作中に発生するエラーをより詳細に表現するためにOpErrorという構造体が定義されています。

type OpError struct {
    Op  string // 操作の種類 (例: "read", "write", "dial", "listen")
    Net string // ネットワークの種類 (例: "tcp", "udp", "ip")
    Addr Addr  // ネットワークアドレス
    Err error  // 元のエラー
}

func (e *OpError) Error() string {
    // エラーメッセージの生成ロジック
}

OpErrorは、どの操作(Op)、どのネットワーク(Net)、どのネットワークアドレス(Addr)で、どのような基底のエラー(Err)が発生したかという情報を提供します。これにより、開発者はエラーの原因をより正確に特定し、適切なエラーハンドリングを行うことができます。

DialListen関数

Goのnetパッケージには、ネットワーク接続を確立するためのDial関数群と、ネットワーク接続を待ち受けるためのListen関数群があります。これらは、TCP、UDP、IP、Unixドメインソケットなど、様々なネットワークプロトコルに対応しています。

  • Dial: 指定されたネットワークアドレスへの接続を確立します。
  • Listen: 指定されたネットワークアドレスで着信接続を待ち受けます。

これらの関数は、成功した場合は接続オブジェクト(例: *TCPConn, *UDPConn)とnilエラーを返し、失敗した場合はnil接続オブジェクトとerrorを返します。

技術的詳細

このコミットの主要な変更点は、netパッケージ内の複数のファイルにおいて、DialおよびListen関連の関数がエラーを返す際に、常にnet.OpErrorでラップするように修正されたことです。

変更前は、以下のようなエラーが直接返されるケースがありました。

  • UnknownNetworkError(netProto): ネットワークプロトコルが不明な場合。
  • errMissingAddress: アドレスが指定されていない場合。
  • 基底のerr(例: socketシステムコールからのエラー)。

変更後は、これらのエラーが&OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err}のような形式でOpError構造体に埋め込まれて返されるようになりました。

例えば、src/pkg/net/iprawsock_posix.godialIP関数では、以下のような変更が行われています。

変更前:

	if err != nil {
		return nil, err
	}
	// ...
	default:
		return nil, UnknownNetworkError(netProto)
	}
	// ...
	if raddr == nil {
		return nil, &OpError{"dial", netProto, nil, errMissingAddress}
	}
	// ...
	if err != nil {
		return nil, err
	}

変更後:

	if err != nil {
		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err}
	}
	// ...
	default:
		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: UnknownNetworkError(netProto)}
	}
	// ...
	if raddr == nil {
		return nil, &OpError{Op: "dial", Net: netProto, Addr: nil, Err: errMissingAddress}
	}
	// ...
	if err != nil {
		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err}
	}

この変更により、netパッケージのユーザーは、ネットワーク操作のエラーを処理する際に、常に*net.OpError型を期待できるようになります。これにより、エラーハンドリングのロジックが簡素化され、より堅牢なコードを書くことが可能になります。例えば、エラーがnet.OpErrorであるかどうかをチェックし、その内部のErrフィールドを調べることで、具体的なエラー原因を特定できます。

また、src/pkg/net/ipsock_posix.gointernetSocket関数では、エラーをOpErrorでラップするロジックが削除されています。これは、internetSocketを呼び出す上位の関数(DialIP, ListenIPなど)が、すでにOpErrorでラップする責任を持つようになったためです。これにより、エラーラップの重複が避けられ、コードの責務が明確化されています。

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

このコミットで変更された主要なファイルと、その中での変更の性質は以下の通りです。

  • src/pkg/net/iprawsock_posix.go: DialIPおよびListenIP関数内で、返されるエラーがnet.OpErrorでラップされるように修正。
  • src/pkg/net/ipsock_posix.go: internetSocket関数から、エラーをOpErrorでラップするロジックが削除。これは、呼び出し元でラップするようになったため。
  • src/pkg/net/tcpsock_posix.go: DialTCPおよびListenTCP関数内で、返されるエラーがnet.OpErrorでラップされるように修正。
  • src/pkg/net/udpsock_posix.go: DialUDP, ListenUDP, ListenMulticastUDP関数内で、返されるエラーがnet.OpErrorでラップされるように修正。
  • src/pkg/net/unixsock_posix.go: DialUnix, ListenUnix, ListenUnixgram関数内で、返されるエラーがnet.OpErrorでラップされるように修正。また、unixSocket関数からもエラーラップロジックが削除。

これらの変更は、主にreturn nil, errとなっていた箇所をreturn nil, &OpError{Op: ..., Net: ..., Addr: ..., Err: err}に置き換える形で行われています。

コアとなるコードの解説

変更の核心は、netパッケージ内の様々なDialおよびListen関数のエラーパスにnet.OpErrorの生成と返却を組み込んだことです。

例えば、src/pkg/net/iprawsock_posix.godialIP関数における以下の変更を見てみましょう。

--- a/src/pkg/net/iprawsock_posix.go
+++ b/src/pkg/net/iprawsock_posix.go
@@ -189,19 +189,19 @@ func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) {
 func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, error) {
 	net, proto, err := parseNetwork(netProto)
 	if err != nil {
-		return nil, err
+		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err}
 	}
 	switch net {
 	case "ip", "ip4", "ip6":
 	default:
-		return nil, UnknownNetworkError(netProto)
+		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: UnknownNetworkError(netProto)}
 	}
 	if raddr == nil {
-		return nil, &OpError{"dial", netProto, nil, errMissingAddress}
+		return nil, &OpError{Op: "dial", Net: netProto, Addr: nil, Err: errMissingAddress}
 	}
 	fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial", sockaddrToIP)
 	if err != nil {
-		return nil, err
+		return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err}
 	}
 	return newIPConn(fd), nil
 }

この差分では、parseNetworkからのエラー、不明なネットワークプロトコル、アドレスの欠落、およびinternetSocketからのエラーのいずれの場合でも、OpErrorが構築され、その中に元のエラーがErrフィールドとして埋め込まれています。

特に注目すべきは、OpErrorAddrフィールドに、操作の種類(dialまたはlisten)に応じて適切なアドレス(raddrまたはladdr)が設定されている点です。これにより、エラー発生時のコンテキストがより豊かになります。

また、src/pkg/net/ipsock_posix.gointernetSocket関数からは、以前存在したOpErrorによるラップ処理が削除されています。

--- a/src/pkg/net/ipsock_posix.go
+++ b/src/pkg/net/ipsock_posix.go
@@ -133,18 +133,7 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family
 
  func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
  	family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
--	fd, err = socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, toAddr)
--	if err != nil {
--		goto Error
--	}
--	return fd, nil
--
--Error:
--	addr := raddr
--	if mode == "listen" {
--		addr = laddr
--	}
--	return nil, &OpError{mode, net, addr, err}
-+	return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, toAddr)
  }
 
  func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, error) {

この変更は、エラーのラップ処理をより高レベルのDialListen関数に集約することで、エラーハンドリングのロジックをシンプルにし、重複を避けるためのリファクタリングです。これにより、internetSocketは純粋にソケットの作成と設定に専念し、エラーのコンテキスト付与は呼び出し元に任されることになります。

関連リンク

  • Go言語のnetパッケージドキュメント: https://pkg.go.dev/net
  • Go言語のエラーハンドリングに関する公式ブログ記事など(一般的な情報源として)

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語のIssueトラッカー (Issue #4856): https://github.com/golang/go/issues/4856 (ただし、このIssueはクローズされており、詳細な議論はGoのコードレビューシステムであるGerritのCL (Change-List) にある可能性が高いです。)
  • Go Gerrit CL 12916046: https://golang.org/cl/12916046 (これはコミットメッセージに記載されているCLへのリンクです。通常、より詳細な議論やレビューコメントが含まれています。)