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

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

このコミットは、Go言語の標準ライブラリ net パッケージにおける DialTCP 関数の挙動を修正し、特にARMアーキテクチャでのビルド問題を解決するためのものです。既存の回避策を拡張し、DialTCPがリモートアドレス(raddr)をnilとして返す可能性のある、特定かつ不明な条件下での問題を緩和することを目的としています。

コミット

commit 152d806b169a54564a21fd91d5cd3fb3cee1a5dc
Author: Rob Pike <r@golang.org>
Date:   Tue Feb 21 16:48:05 2012 +1100

    net: extend the workaround to DialTCP to try to get arm building again.
    Awfulness by the bucket that we hope
    Fixes #3057.
    
    R=golang-dev, mikioh.mikioh, dsymonds, r, rsc
    CC=golang-dev
    https://golang.org/cl/5687060

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

https://github.com/golang/go/commit/152d806b169a54564a21fd91d5cd3fb3cee1a5dc

元コミット内容

このコミットは、netパッケージのDialTCP関数における既存の回避策を拡張し、特にARMアーキテクチャでのビルドが再び可能になるようにするためのものです。コミットメッセージには「Awfulness by the bucket that we hope」(願わくば、このひどい状況が改善されることを)という表現があり、当時の問題の複雑さと、この変更が根本的な解決ではなく一時的な回避策であることを示唆しています。また、Fixes #3057と記載されており、GoのIssueトラッカーのIssue 3057を修正するものであることがわかります。

変更の背景

この変更の背景には、Go言語のnetパッケージ、特にDialTCP関数が、特定の条件下(特にARMアーキテクチャ環境下)で、確立されたTCP接続のリモートアドレス(raddr)をnilとして返すというバグがありました。通常、接続が成功した場合、リモートアドレスは常に有効な値を持つことが期待されます。しかし、このバグにより、net.ConnインターフェースのRemoteAddr()メソッドがnilを返し、アプリケーションが予期せぬエラーやパニックを引き起こす可能性がありました。

Issue #3057では、この問題が報告されており、DialTCPが内部的に呼び出すGetpeernameシステムコールが、一部のプラットフォーム(特にARM)で失敗することが原因であると推測されていました。Getpeernameはソケットのピア(接続相手)のアドレスを取得するためのシステムコールですが、そのエラーが適切に処理されず、結果としてraddrnilになるケースがあったようです。

このコミットは、このnil raddr問題に対する既存の回避策をさらに強化し、特にARM環境でのGoのビルドと実行を安定させることを目的としています。コミットメッセージの「Awfulness by the bucket that we hope」という表現は、この問題が根本的な原因が特定しにくい、厄介なバグであり、この変更が完全な解決ではなく、一時的な対処療法であることを示唆しています。

前提知識の解説

  • netパッケージ: Go言語の標準ライブラリで、ネットワークI/O機能を提供します。TCP/UDP接続、DNSルックアップなどが含まれます。
  • DialTCP関数: netパッケージの一部で、指定されたネットワークアドレス(laddr:ローカルアドレス、raddr:リモートアドレス)を使用してTCP接続を確立するために使用されます。成功すると*TCPConnオブジェクトを返します。
  • *TCPConn: 確立されたTCP接続を表す構造体です。RemoteAddr()メソッドを持ち、接続先のリモートアドレスを返します。
  • sockaddr: ソケットアドレスを表す汎用的なデータ構造です。OSのシステムコール(例: bind, connect, getpeername)で使用されます。
  • Getpeernameシステムコール: 接続されたソケットのピア(リモートエンド)のアドレスを取得するために使用されるPOSIXシステムコールです。
  • selfConnect: ネットワーク接続が自分自身(ローカルホスト)に対して行われたかどうかを検出するためのロジックです。これは、特定のネットワークプロトコルやテストシナリオで重要になることがあります。
  • ARMアーキテクチャ: スマートフォン、タブレット、組み込みシステムなどで広く使用されているプロセッサアーキテクチャです。特定のシステムコールやカーネルの挙動が、他のアーキテクチャ(x86など)と異なる場合があります。
  • panic: Go言語における回復不可能なエラー状態です。通常、プログラムの実行を停止させます。開発中のデバッグ目的で一時的に挿入されることがあります。
  • 回避策 (Workaround): 問題の根本原因を解決するのではなく、その影響を一時的に回避するための対処法です。

技術的詳細

このコミットは、主にsrc/pkg/net/ipsock_posix.gosrc/pkg/net/tcpsock_posix.goの2つのファイルに変更を加えています。

  1. src/pkg/net/ipsock_posix.goの変更:

    • internetSocket関数内で、ra == nilの場合にpanicを引き起こしていたデバッグ用のコードが削除されました。このpanicは、selfConnectのデバッグ中にraddrnilになる状況を特定するために一時的に追加されていたものです。この削除は、この特定のデバッグ用チェックがもはや必要ないか、あるいはより広範な回避策の一部として統合されたことを示唆しています。
  2. src/pkg/net/tcpsock_posix.goの変更:

    • sockaddrToTCP関数内のpanicメッセージが変更されました。以前は「TODO(r): Diagnose when we will turn a non-nil sockaddr into a nil. Part of diagnosing the selfConnect bug.」というデバッグ目的のコメントが含まれていましたが、これが「Diagnose when we will turn a non-nil sockaddr into a nil.」と簡潔になり、デバッグの段階から一般的な診断メッセージへと変更されています。
    • DialTCP関数から、checkRaddrというヘルパー関数とその呼び出しが削除されました。このcheckRaddr関数もまた、fd.raddrnilである場合にpanicを引き起こすデバッグ用のチェックでした。この削除は、ipsock_posix.goでの変更と同様に、この特定のデバッグ用チェックが不要になったことを意味します。
    • 最も重要な変更は、selfConnect関数内に追加された新しいロジックです。
      func selfConnect(fd *netFD) bool {
          // The socket constructor can return an fd with raddr nil under certain
          // unknown conditions. The errors in the calls there to Getpeername
          // are discarded, but we can't catch the problem there because those
          // calls are sometimes legally erroneous with a "socket not connected".
          // Since this code (selfConnect) is already trying to work around
          // a problem, we make sure if this happens we recognize trouble and
          // ask the DialTCP routine to try again.
          // TODO: try to understand what's really going on.
          if fd.laddr == nil || fd.raddr == nil {
              return true
          }
          l := fd.laddr.(*TCPAddr)
          r := fd.raddr.(*TCPAddr)
          return l.Port == r.Port && l.IP.Equal(r.IP)
      }
      
      この新しいコードは、fd.laddrまたはfd.raddrのいずれかがnilである場合、selfConnect関数がtrueを返すようにします。selfConnecttrueを返すと、DialTCP関数内のループが再試行をトリガーします。これにより、internetSocketnil raddrを返すような「未知の条件」が発生した場合でも、DialTCPが接続を再試行し、最終的に有効なraddrを持つ接続を確立できる可能性が高まります。 コメントには、「ソケットコンストラクタが、特定の未知の条件下でraddrnilfdを返すことがある」と明記されており、Getpeername呼び出しでのエラーが破棄されるため、問題が捕捉しにくいことが説明されています。この変更は、根本原因が不明なまま、nil raddrの問題を回避するための「回避策の拡張」として機能します。

これらの変更は、DialTCPnil raddrを返すという特定のバグに対する、より堅牢な回避策を導入しています。特にARMアーキテクチャでこの問題が頻繁に発生していたため、この変更によってARM環境でのGoのネットワーク機能の安定性が向上し、ビルドが再び成功するようになったと考えられます。

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

src/pkg/net/ipsock_posix.go

--- a/src/pkg/net/ipsock_posix.go
+++ b/src/pkg/net/ipsock_posix.go
@@ -117,10 +117,6 @@ func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode s
 		if ra, oserr = raddr.sockaddr(family); oserr != nil {
 			goto Error
 		}
-		if ra == nil {
-			// TODO(r): part of selfConnect debugging
-			panic("ra nil when raddr non-nil")
-		}
 	}
 	fd, oserr = socket(net, family, sotype, proto, la, ra, toAddr)
 	if oserr != nil {

src/pkg/net/tcpsock_posix.go

--- a/src/pkg/net/tcpsock_posix.go
+++ b/src/pkg/net/tcpsock_posix.go
@@ -29,8 +29,7 @@ func sockaddrToTCP(sa syscall.Sockaddr) Addr {
 		return &TCPAddr{sa.Addr[0:], sa.Port}
 	default:
 		if sa != nil {
-			// TODO(r): Diagnose when we will turn a non-nil sockaddr into a nil.
-			// Part of diagnosing the selfConnect bug.
+			// Diagnose when we will turn a non-nil sockaddr into a nil.
 			panic(fmt.Sprintf("unexpected type in sockaddrToTCP: %T", sa))
 		}
 	}
@@ -237,13 +236,6 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
 
 	fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP)
 
-	checkRaddr := func(s string) {
-		if err == nil && fd.raddr == nil {
-			panic("nil raddr in DialTCP: " + s)
-		}
-	}
-	checkRaddr("early")
-
 	// TCP has a rarely used mechanism called a 'simultaneous connection' in
 	// which Dial("tcp", addr1, addr2) run on the machine at addr1 can
 	// connect to a simultaneous Dial("tcp", addr2, addr1) run on the machine
@@ -264,7 +256,6 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
 	for i := 0; i < 2 && err == nil && laddr == nil && selfConnect(fd); i++ {
 		fd.Close()
 		fd, err = internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP)
-		checkRaddr("after close")
 	}
 
 	if err != nil {
@@ -274,6 +265,17 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
 }
 
 func selfConnect(fd *netFD) bool {
+	// The socket constructor can return an fd with raddr nil under certain
+	// unknown conditions. The errors in the calls there to Getpeername
+	// are discarded, but we can't catch the problem there because those
+	// calls are sometimes legally erroneous with a "socket not connected".
+	// Since this code (selfConnect) is already trying to work around
+	// a problem, we make sure if this happens we recognize trouble and
+	// ask the DialTCP routine to try again.
+	// TODO: try to understand what's really going on.
+	if fd.laddr == nil || fd.raddr == nil {
+		return true
+	}
 	l := fd.laddr.(*TCPAddr)
 	r := fd.raddr.(*TCPAddr)
 	return l.Port == r.Port && l.IP.Equal(r.IP)

コアとなるコードの解説

このコミットの核心は、selfConnect関数に新しい条件分岐を追加し、DialTCP関数内のデバッグ用paniccheckRaddrヘルパー関数を削除した点にあります。

  1. デバッグ用panicの削除:

    • src/pkg/net/ipsock_posix.gointernetSocket関数から、ra == nilの場合に発生するpanicが削除されました。これは、selfConnectのデバッグ中にraddrnilになる状況を特定するためのものでした。
    • src/pkg/net/tcpsock_posix.goDialTCP関数から、checkRaddr関数とその呼び出しが削除されました。これもfd.raddrnilである場合にpanicを引き起こすデバッグ用のチェックでした。 これらの削除は、問題の診断フェーズが終わり、より広範な回避策が導入されたことを示しています。
  2. selfConnect関数の変更:

    • selfConnect関数に以下の新しいロジックが追加されました。
      if fd.laddr == nil || fd.raddr == nil {
          return true
      }
      
    • この変更は、netFDオブジェクトのローカルアドレス(laddr)またはリモートアドレス(raddr)のいずれかがnilである場合、selfConnect関数がtrueを返すようにします。
    • DialTCP関数は、selfConnect(fd)trueを返す限り、接続の再試行ループに入ります。これにより、internetSocketが何らかの理由でnil raddrを持つfdを返した場合でも、DialTCPは接続を閉じ、再試行することで、最終的に有効なアドレスを持つ接続を確立しようとします。
    • このロジックは、Getpeernameシステムコールがエラーを破棄し、raddrnilになる「未知の条件」に対する直接的な回避策です。特にARMアーキテクチャでこの問題が顕著であったため、この変更はARM環境でのネットワーク接続の安定性を大幅に向上させることが期待されました。

要するに、このコミットは、デバッグ用の厳格なチェックを削除し、代わりに、nilアドレスという異常な状態をselfConnectが検出し、DialTCPに接続の再試行を促すという、より柔軟な(しかし根本原因を解決しない)回避策を導入しています。

関連リンク

参考にした情報源リンク