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

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

このコミットは、Go言語のランタイム、syscallパッケージ、およびos/signalパッケージにおけるWindowsビルドの問題を修正することを目的としています。具体的には、Windows環境でのシグナルハンドリングの堅牢性を向上させ、関連するテストを追加・修正しています。これにより、GoプログラムがWindows上でより安定して動作し、シグナルイベント(特にCtrl+Breakのようなコンソールイベント)を適切に処理できるようになります。

コミット

commit 07a2989d17d6a5ad9c46e3213f66d01761cf5cd3
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Tue Feb 14 13:51:38 2012 +1100

    runtime, syscall, os/signal: fix windows build
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5656048

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

https://github.com/golang/go/commit/07a2989d17d6a5ad9c46e3213f66d01761cf5cd3

元コミット内容

runtime, syscall, os/signal: fix windows build

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

変更の背景

このコミットが行われた背景には、Go言語がWindowsプラットフォームで安定して動作するための課題がありました。特に、シグナルハンドリングのメカニズムはOSによって大きく異なり、Unix系システムとWindowsではその実装が根本的に異なります。Goの初期段階では、Unix系システムに重点が置かれていたため、Windows固有のシグナル処理(例えば、Ctrl+CやCtrl+Breakといったコンソールイベント)が十分に考慮されていませんでした。

このコミットは、以下の問題に対処しています。

  1. Windowsビルドの不安定性: GoのランタイムやsyscallパッケージがWindows環境で正しくビルドされず、実行時に問題が発生する可能性がありました。
  2. シグナルハンドリングの不備: Windowsにおけるシグナル(特にコンソールイベント)の処理が不完全であり、Goプログラムがこれらのイベントに適切に応答できない、または予期せぬ動作をする可能性がありました。
  3. テストカバレッジの不足: Windows固有のシグナルハンドリングに関するテストが不足しており、変更が正しく機能するかどうかを確認する手段が限られていました。

これらの問題を解決し、GoがWindows環境でより堅牢に動作するようにするために、ランタイム、syscallos/signalパッケージにわたる修正と、Windows固有のシグナルテストの追加が必要とされました。

前提知識の解説

このコミットを理解するためには、以下の技術的背景知識が役立ちます。

  1. Goランタイム (Go Runtime): Go言語のプログラムは、Goランタイム上で動作します。ランタイムは、ガベージコレクション、スケジューリング(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなど、プログラムの実行に必要な低レベルの機能を提供します。OSとのインタラクションはランタイムの重要な役割の一つです。

  2. OSシグナル (Operating System Signals): シグナルは、オペレーティングシステムがプロセスに対して非同期に通知を送信するメカニズムです。これにより、プロセスは特定のイベント(例: ユーザーによるCtrl+Cの押下、不正なメモリアクセス、タイマーの期限切れなど)に応答できます。

    • Unix系システム: SIGINT (Ctrl+C), SIGTERM, SIGKILL, SIGSEGVなど、POSIX標準で定義されたシグナルが一般的です。
    • Windowsシステム: WindowsはUnix系とは異なるシグナルメカニズムを持っています。特に、コンソールアプリケーションでは「コンソール制御イベント (Console Control Events)」が使用されます。これには CTRL_C_EVENT (Ctrl+C), CTRL_BREAK_EVENT (Ctrl+Break), CTRL_CLOSE_EVENT (ウィンドウを閉じる), CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT などがあります。これらのイベントは、SetConsoleCtrlHandler 関数によって登録されたハンドラ関数によって処理されます。
  3. Goのsyscallパッケージ: syscallパッケージは、Goプログラムからオペレーティングシステムの低レベルなプリミティブ(システムコール)にアクセスするためのインターフェースを提供します。これにより、ファイル操作、ネットワーク通信、プロセス管理、シグナルハンドリングなど、OS固有の機能を利用できます。このパッケージはOSごとに異なる実装を持ちます(例: syscall_windows.go, syscall_unix.go)。

  4. Goのos/signalパッケージ: os/signalパッケージは、OSシグナルをGoのチャネルを通じて受信するための高レベルなインターフェースを提供します。これにより、開発者はOSシグナルをGoの並行処理モデルに統合し、ゴルーチンを使ってシグナルイベントを処理できます。

  5. Goのビルドタグ (+build): Goのソースファイルには、+buildディレクティブを使用してビルドタグを指定できます。これにより、特定のOS、アーキテクチャ、またはカスタムタグに基づいて、どのファイルをビルドに含めるかを制御できます。例えば、// +build windowsは、そのファイルがWindowsビルドでのみコンパイルされることを示します。

  6. Windows API (Kernel32.dll, GenerateConsoleCtrlEvent): Windowsアプリケーションは、Windows APIを通じてOSの機能を利用します。kernel32.dllは、Windowsのコアシステム機能を提供する重要なダイナミックリンクライブラリ(DLL)です。GenerateConsoleCtrlEvent関数は、指定されたプロセスグループにコンソール制御イベントを送信するために使用されます。これは、プログラム的にCtrl+CやCtrl+Breakイベントをシミュレートする際に利用されます。

技術的詳細

このコミットは、GoのWindowsビルドにおけるシグナルハンドリングの複数の側面を修正・改善しています。

  1. os/signal/signal_unix.go のビルドタグ変更: signal_unix.goは、元々Unix系OS(darwin, freebsd, linux, netbsd, openbsd)向けにビルドされるように設定されていました。このコミットでは、+build windowsタグが追加され、このファイルがWindowsでもビルドされるようになりました。これは、os/signalパッケージの共通部分がWindowsでも利用されるようにするため、またはWindows固有のシグナル処理をUnix系のシグナル処理と統合するための変更の一部と考えられます。

  2. os/signal/signal_windows_test.go の新規追加: このファイルは、Windows固有のシグナルハンドリング、特にCtrl+Breakイベントのテストのために新規追加されました。

    • sendCtrlBreak関数: kernel32.dllからGenerateConsoleCtrlEvent関数をロードし、現在のプロセスグループにCTRL_BREAK_EVENTを送信します。これにより、プログラム的にCtrl+Breakシグナルを発生させることができます。
    • TestCtrlBreak関数: sendCtrlBreakを使用してCtrl+Breakシグナルを発生させ、os/signalパッケージのNotify関数を通じてシグナルが正しく受信されることを検証します。os.Interruptとしてシグナルがチャネルに送られることを期待しています。これは、WindowsのCtrl+BreakがGoの内部でos.Interruptにマッピングされていることを示唆しています。
  3. src/pkg/net/dial_test.go のWindowsでのテストスキップ: TestSelfConnectテストがWindows上でハングアップする問題があったため、runtime.GOOS == "windows"の場合にこのテストをスキップするようになりました。これは、一時的な回避策であり、根本的な原因の調査と修正が将来的に必要であることを示唆しています。

  4. src/pkg/runtime/os_windows.h の変更: NSIGマクロが#define NSIG 65として追加されました。NSIGは、システムがサポートするシグナルの最大数を定義する定数です。Windows固有のランタイムヘッダーにこの定義を追加することで、シグナル関連の配列やデータ構造のサイズが適切に設定されるようになります。コメントの「// TODO(brainman): should not need those」は、将来的にはこの定義が不要になるような、より洗練されたシグナルハンドリングメカニズムが望ましいという開発者の意図を示しています。

  5. src/pkg/runtime/runtime.h から runtime·siginit の削除: runtime.hからvoid runtime·siginit(void);の宣言が削除されました。これは、シグナル初期化のロジックが変更され、runtime·siginitが直接呼び出されるのではなく、別の場所(おそらくthread_windows.c内のruntime·initsig)から間接的に、または異なる方法で初期化されるようになったことを示唆しています。

  6. src/pkg/runtime/signal_windows_386.c および src/pkg/runtime/signal_windows_amd64.c の変更: これらのファイルから、runtime·initsig関数とruntime·sigtrampに関連するコードが削除されました。これは、シグナルハンドリングの初期化ロジックがこれらのアーキテクチャ固有のファイルから、より汎用的な場所(thread_windows.c)に移動されたことを意味します。これにより、コードの重複が減り、シグナル初期化の管理が一元化されます。

  7. src/pkg/runtime/sigqueue.goc のコメント修正: コメントが「The initial state is that sig.note is cleared (setup by siginit).」から「The initial state is that sig.note is cleared (setup by signal_enable).」に変更されました。これは、シグナル初期化の概念がsiginitからsignal_enableというより具体的な関数名に変わったことを反映しています。

  8. src/pkg/runtime/thread_windows.c の変更: runtime·initsig関数がこのファイルに移動され、runtime·sigtrampへの参照が保持されるようになりました。

    extern void *runtime·sigtramp;
    
    void
    runtime·initsig(void)
    {
        // following line keeps sigtramp alive at link stage
        // if there's a better way please write it here
        void *p = runtime·sigtramp;
        USED(p);
    }
    

    runtime·sigtrampは、シグナルハンドラが呼び出される際のジャンプテーブルまたはエントリポイントのような役割を果たす可能性があります。このコードは、リンカがruntime·sigtrampを最適化によって削除しないようにするためのトリックです。シグナルハンドリングの初期化ロジックがこのファイルに集約されたことで、Windowsスレッドとシグナル処理の関連性が明確になりました。

  9. src/pkg/syscall/syscall_windows.go の変更:

    • WaitStatus構造体のSignal()およびStopSignal()メソッドの戻り値の型がintから新しく定義されたSignal型に変更されました。
    • Signalという新しい型が定義されました。これはintのエイリアスですが、Signal()メソッドとString()メソッドを持つことで、より型安全で表現豊かなシグナル表現を提供します。
      • func (s Signal) Signal() {}: os.Signalインターフェースを満たすためのダミーメソッド。
      • func (s Signal) String() string: シグナル値を人間が読める文字列に変換するメソッド。ztypes_windows.goで定義されるsignals配列を使用します。
  10. src/pkg/syscall/ztypes_windows.go の変更:

    • シグナル定数(SIGHUP, SIGINTなど)の型がintから新しく定義されたSignal型に変更されました(例: SIGHUP = Signal(0x1))。これにより、シグナル値がより厳密に型付けされ、syscallパッケージ内で一貫して扱われるようになります。
    • signalsという文字列配列が追加されました。これは、シグナル値に対応する人間が読める名前を提供します。syscall_windows.goSignal.String()メソッドで利用されます。

これらの変更は全体として、GoのWindowsビルドにおけるシグナルハンドリングの基盤を強化し、より正確で堅牢なシグナル処理を可能にしています。特に、Signal型の導入は、シグナルを単なる整数値として扱うのではなく、意味のある型として扱うことで、コードの可読性と保守性を向上させています。

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

このコミットのコアとなるコードの変更箇所は、主に以下のファイルに集中しています。

  1. src/pkg/os/signal/signal_windows_test.go (新規追加): Windows固有のシグナルハンドリング(特にCtrl+Break)のテストを追加し、Goがこれらのイベントを正しく処理できることを検証します。

    // 新規ファイル
    package signal
    
    import (
        "flag"
        "os"
        "syscall"
        "testing"
        "time"
    )
    
    var runCtrlBreakTest = flag.Bool("run_ctlbrk_test", false, "force to run Ctrl+Break test")
    
    func sendCtrlBreak(t *testing.T) {
        d, e := syscall.LoadDLL("kernel32.dll")
        if e != nil {
            t.Fatalf("LoadDLL: %v\n", e)
        }
        p, e := d.FindProc("GenerateConsoleCtrlEvent")
        if e != nil {
            t.Fatalf("FindProc: %v\n", e)
        }
        r, _, e := p.Call(0, 0) // CTRL_BREAK_EVENT, 0 (process group)
        if r == 0 {
            t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
        }
    }
    
    func TestCtrlBreak(t *testing.T) {
        if !*runCtrlBreakTest {
            t.Logf("test disabled; use -run_ctlbrk_test to enable")
            return
        }
        go func() {
            time.Sleep(1 * time.Second)
            sendCtrlBreak(t)
        }()
        c := make(chan os.Signal, 10)
        Notify(c) // os/signal.Notify
        select {
        case s := <-c:
            if s != os.Interrupt {
                t.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
            }
        case <-time.After(3 * time.Second):
            t.Fatalf("Timeout waiting for Ctrl+Break\n")
        }
    }
    
  2. src/pkg/syscall/syscall_windows.go: WaitStatusのメソッドの戻り値の型をintからSignalに変更し、Signal型自体を定義しています。

    // 変更前: func (w WaitStatus) Signal() int { return -1 }
    // 変更後: func (w WaitStatus) Signal() Signal { return -1 }
    
    // 変更前: func (w WaitStatus) StopSignal() int { return -1 }
    // 変更後: func (w WaitStatus) StopSignal() Signal { return -1 }
    
    // 新規追加:
    type Signal int
    
    func (s Signal) Signal() {} // os.Signal インターフェースを満たす
    func (s Signal) String() string {
        if 0 <= s && int(s) < len(signals) {
            str := signals[s]
            if str != "" {
                return str
            }
        }
        return "signal " + itoa(int(s))
    }
    
  3. src/pkg/syscall/ztypes_windows.go: シグナル定数の型をSignalに変更し、シグナル名に対応する文字列配列signalsを追加しています。

    // 変更前: SIGHUP = 0x1
    // 変更後: SIGHUP = Signal(0x1)
    // ...他のシグナル定数も同様に変更
    
    // 新規追加:
    var signals = [...]string{
        1:  "hangup",
        2:  "interrupt",
        3:  "quit",
        4:  "illegal instruction",
        5:  "trace/breakpoint trap",
        6:  "aborted",
        7:  "bus error",
        8:  "floating point exception",
        9:  "killed",
        10: "user defined signal 1",
        11: "segmentation fault",
        12: "user defined signal 2",
        13: "broken pipe",
        14: "alarm clock",
        15: "terminated",
    }
    

コアとなるコードの解説

このコミットの核となる変更は、Windowsにおけるシグナルハンドリングの型安全性の向上テストカバレッジの拡大です。

  1. os/signal/signal_windows_test.go の追加: このテストファイルは、GoがWindowsのコンソールイベント(特にCtrl+Break)を正しくシグナルとして認識し、os/signalパッケージを通じてGoプログラムに通知できることを保証します。GenerateConsoleCtrlEvent APIを直接呼び出すことで、実際のOSイベントをシミュレートし、Goのシグナルハンドリングメカニズムが期待通りに機能するかを検証しています。これにより、Windows環境でのシグナル処理に関する回帰バグを防ぎ、将来の変更に対する安全網を提供します。

  2. syscall.Signal 型の導入と利用: 以前は、シグナルは単なるint型として扱われていました。しかし、syscall/syscall_windows.gotype Signal intとして新しい型を定義し、関連するメソッド(Signal(), String())を追加することで、シグナルがより意味のあるデータ型として扱われるようになりました。

    • Signal()メソッドは、os.Signalインターフェースを満たすために必要です。これにより、syscall.Signal型の値がos.Signalパッケージの関数(例: os/signal.Notify)に渡せるようになります。
    • String()メソッドは、シグナル値を人間が読める文字列に変換する機能を提供します。これはデバッグやログ出力において非常に有用です。
    • syscall/ztypes_windows.goでシグナル定数(SIGHUPなど)がSignal(0x1)のようにSignal型にキャストされたことで、コンパイル時に型チェックが強化され、誤ったシグナル値の使用を防ぐことができます。また、signals配列の導入により、シグナル値と文字列表現の対応が一元的に管理されるようになりました。

これらの変更は、GoのWindowsサポートを成熟させる上で重要なステップです。シグナルハンドリングは、アプリケーションの正常な終了、エラー処理、およびユーザーインタラクションにおいて不可欠な要素であり、その堅牢性はOSサポートの品質に直結します。

関連リンク

参考にした情報源リンク