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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージにおける、非Unix系システムでのビルド問題を修正するものです。具体的には、IPv6スタックの検出処理が、特定の環境でビルドエラーを引き起こす可能性があったため、その初期化タイミングを変更しています。

コミット

  • コミットハッシュ: 5451708d5be8a826cd1753fe2be611bc4d278059
  • 作者: Mikio Hara mikioh.mikioh@gmail.com
  • コミット日時: 2012年11月9日 金曜日 02:09:09 +0900

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

https://github.com/golang/go/commit/5451708d5be8a826cd1753fe2be611bc4d278059

元コミット内容

net: fix non-unixen build

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

変更の背景

このコミットの主な背景は、Go言語のnetパッケージが、Unix系ではないオペレーティングシステム(例えばWindowsなど)でビルドされる際に発生していた問題の解決です。

Goのnetパッケージは、ネットワーク関連の機能を提供し、その中にはIPv6のサポート状況をシステムから検出するprobeIPv6Stack()のような関数が含まれています。元のコードでは、supportsIPv6supportsIPv4mapというグローバル変数が、パッケージの初期化時に直接probeIPv6Stack()の戻り値で初期化されていました。

しかし、一部の非Unix系環境では、グローバル変数の初期化フェーズでprobeIPv6Stack()が呼び出されると、必要なシステムリソースやライブラリがまだ完全に利用可能になっていないために、ビルドエラーやランタイムエラーが発生する可能性がありました。これは、Goのリンカが、グローバル変数の初期化時に特定の外部シンボルやシステムコールへの依存関係を解決できない場合に顕著になります。

この問題を解決するため、probeIPv6Stack()の呼び出しを、Goのinit()関数内に移動することで、より安全な初期化タイミングを確保する必要がありました。init()関数は、パッケージ内のすべてのグローバル変数が初期化された後に実行されることが保証されているため、システム依存の初期化処理を行うのに適しています。

前提知識の解説

  • Go言語のnetパッケージ: Go言語の標準ライブラリの一部で、TCP/IPネットワーク通信、UDP、DNSルックアップなど、様々なネットワーク機能を提供するパッケージです。
  • IPv6 (Internet Protocol Version 6): インターネットプロトコルの最新バージョンで、IPv4アドレスの枯渇問題に対応するために設計されました。より大きなアドレス空間を持ち、効率的なルーティングやセキュリティ機能が強化されています。
  • probeIPv6Stack(): netパッケージ内部で使用される関数で、実行中のシステムがIPv6スタックをサポートしているかどうか、またIPv4-mapped IPv6アドレス(IPv6アドレス空間内でIPv4アドレスを表現する形式)をサポートしているかどうかを検出します。この検出は、システムコールや特定のネットワークインターフェース情報の取得を伴う場合があります。
  • Go言語のinit()関数: Go言語のパッケージには、init()という特別な関数を定義できます。この関数は、パッケージがインポートされ、そのパッケージ内のすべてのグローバル変数が初期化された後に、自動的に一度だけ実行されます。複数のinit()関数がある場合、それらは定義された順序で実行されます。これは、パッケージ固有の初期化ロジック(例:設定の読み込み、リソースのセットアップ、システム依存のチェックなど)を実行するのに非常に便利です。
  • グローバル変数の初期化: Goでは、パッケージレベルで宣言された変数は、プログラムの起動時に初期化されます。この初期化は、init()関数が実行されるよりも前に行われます。変数の初期化式が関数呼び出しを含む場合、その関数は初期化フェーズで実行されます。
  • 非Unix系ビルド: Goはクロスプラットフォーム対応の言語であり、Windows、macOS、Linuxなど様々なOSで動作します。しかし、OS固有のシステムコールやライブラリに依存するコードは、プラットフォーム間で互換性の問題を引き起こすことがあります。特に、ネットワーク関連の低レベルな処理は、OSのAPIの違いに影響されやすいです。

技術的詳細

このコミットの技術的な核心は、Go言語におけるパッケージの初期化順序と、OS固有の依存関係の取り扱いに関するものです。

元のコードでは、src/pkg/net/ipsock.goファイル内で、supportsIPv6supportsIPv4mapという2つのブール型グローバル変数が、以下のように宣言と同時にprobeIPv6Stack()関数の結果で初期化されていました。

var supportsIPv6, supportsIPv4map = probeIPv6Stack()

Goの仕様では、パッケージレベルの変数は、そのパッケージのinit()関数が実行される前に初期化されます。probeIPv6Stack()関数は、内部的にOSのネットワークスタックの状態を問い合わせるためのシステムコールや、特定のライブラリへの依存を持つ可能性があります。

非Unix系システム(例えばWindows)では、これらのシステムコールやライブラリが、Goランタイムの初期化の非常に早い段階で、まだ完全に準備ができていない状態で呼び出されると、以下のような問題が発生する可能性がありました。

  1. リンカエラー: probeIPv6Stack()が依存する外部シンボル(C言語のライブラリ関数など)が、グローバル変数の初期化フェーズではまだ解決できない。
  2. ランタイムエラー: 必要なネットワークサービスやデバイスがまだ初期化されていない状態で、probeIPv6Stack()が不正な値を返したり、パニックを引き起こしたりする。

この問題を解決するために、コミットではprobeIPv6Stack()の呼び出しをinit()関数内に移動しました。

var supportsIPv6, supportsIPv4map bool // グローバル変数は宣言のみ

func init() {
    sysInit() // 既存のシステム初期化関数(もしあれば)
    supportsIPv6, supportsIPv4map = probeIPv6Stack() // init関数内で初期化
}

この変更により、supportsIPv6supportsIPv4mapはまずGoのゼロ値(false)で初期化され、その後、パッケージのすべてのグローバル変数が初期化され、かつsysInit()(もしあれば、OS固有の初期化処理)が実行された後に、probeIPv6Stack()が呼び出されて適切な値が設定されるようになります。これにより、probeIPv6Stack()が実行される時点では、システム環境がより安定した状態にあることが保証され、非Unix系システムでのビルドや実行時の安定性が向上します。

sysInit()関数は、このコミットの差分には含まれていませんが、Goのnetパッケージのような低レベルなパッケージでは、OS固有の初期化処理(例:Winsockの初期化など)を行うための関数が存在することが一般的です。init()関数内でsysInit()を呼び出すことで、probeIPv6Stack()が実行される前に、必要なシステムレベルの準備が整うことを保証しています。

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

変更はsrc/pkg/net/ipsock.goファイルに集中しています。

--- a/src/pkg/net/ipsock.go
+++ b/src/pkg/net/ipsock.go
@@ -8,7 +8,12 @@ package net
 
 import "time"
 
-var supportsIPv6, supportsIPv4map = probeIPv6Stack()
+var supportsIPv6, supportsIPv4map bool
+
+func init() {
+	sysInit()
+	supportsIPv6, supportsIPv4map = probeIPv6Stack()
+}
 
 func firstFavoriteAddr(filter func(IP) IP, addrs []string) (addr IP) {
 	if filter == nil {

具体的には以下の変更が行われました。

  1. var supportsIPv6, supportsIPv4map = probeIPv6Stack() の行が削除されました。
  2. var supportsIPv6, supportsIPv4map bool という、初期値を持たないグローバル変数の宣言が追加されました。これにより、これらの変数はGoのデフォルトのゼロ値(false)で初期化されます。
  3. init() 関数が追加され、その中で sysInit() の呼び出しと、supportsIPv6, supportsIPv4map = probeIPv6Stack() による実際の値の代入が行われるようになりました。

コアとなるコードの解説

この変更の核心は、supportsIPv6supportsIPv4mapというグローバル変数の初期化タイミングを、パッケージのinit()関数内に移動した点です。

  • 変更前:

    var supportsIPv6, supportsIPv4map = probeIPv6Stack()
    

    この記述では、netパッケージがロードされる際に、probeIPv6Stack()関数が直ちに実行され、その結果がsupportsIPv6supportsIPv4mapに代入されます。Goのグローバル変数の初期化は、init()関数よりも前に行われるため、probeIPv6Stack()が実行されるタイミングが非常に早くなります。

  • 変更後:

    var supportsIPv6, supportsIPv4map bool
    
    func init() {
        sysInit()
        supportsIPv6, supportsIPv4map = probeIPv6Stack()
    }
    

    変更後では、まずsupportsIPv6supportsIPv4mapbool型のゼロ値であるfalseで初期化されます。 その後、netパッケージのすべてのグローバル変数の初期化が完了した後に、init()関数が自動的に呼び出されます。 init()関数内では、まずsysInit()が呼び出されます。このsysInit()は、OS固有の初期化処理(例えばWindowsにおけるWinsockの初期化など)を行うための関数であると推測されます。これにより、probeIPv6Stack()が実行される前に、システムレベルで必要な準備が整います。 そして、sysInit()の後にprobeIPv6Stack()が呼び出され、その結果がsupportsIPv6supportsIPv4mapに代入されます。

この初期化タイミングの変更により、probeIPv6Stack()が、より安定した、完全に初期化されたシステム環境で実行されることが保証されます。これにより、特に非Unix系システムにおいて、ネットワークスタックのプローブが失敗したり、ビルド時にリンカエラーが発生したりする問題を回避できるようになります。これは、Goのクロスプラットフォーム対応を強化し、より多くの環境で安定して動作するための重要な修正です。

関連リンク

参考にした情報源リンク

  • Go言語のinit関数に関する公式ドキュメントやブログ記事 (一般的なGoの初期化メカニズムの理解のため)
  • Go言語のnetパッケージのソースコード (変更箇所の周辺コードの理解のため)
  • Go言語のクロスコンパイルやプラットフォーム固有のビルドに関する情報 (非Unix系ビルドの背景理解のため)
  • IPv6およびIPv4-mapped IPv6アドレスに関する一般的なネットワーク知識