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

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

このコミットは、Goコンパイラのsrc/cmd/gc/racewalk.cファイルに対する変更です。racewalk.cはGoのデータ競合検出器(Race Detector)の一部であり、コンパイル時にコードに計測(instrumentation)を挿入する役割を担っています。具体的には、このファイルは、データ競合検出器が正確なスタックトレースを収集できるように、特定のパッケージ(syncおよびsync/atomic)の関数呼び出しに対してfuncenter/funcexitの計測を追加するロジックを管理しています。

コミット

このコミットは、sync.Once()atomic.XXXといったsyncおよびsync/atomicパッケージ内の関数呼び出しからスタックトレースが正しく表示されない問題を解決するために行われました。変更の目的は、これらのパッケージ内の関数にfuncenter/funcexitの計測を導入することで、データ競合検出器がより正確なスタックトレース情報を収集できるようにすることです。

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

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

元コミット内容

cmd/gc: racewalk: collect stack traces in sync and sync/atomic
W/o this change stack traces do not show from where sync.Once()
or atomic.XXX was called.
This change add funcenter/exit instrumentation to sync/sync.atomic
packages.

R=golang-dev
CC=golang-dev
https://golang.org/cl/6854112

変更の背景

Goのデータ競合検出器は、並行処理におけるデータ競合を検出するための強力なツールです。データ競合が検出された場合、検出器は問題が発生した場所のスタックトレースを提供し、開発者が問題を特定し修正するのに役立ちます。しかし、このコミット以前は、syncパッケージのsync.Once()sync/atomicパッケージのatomic.AddInt32()などの関数が関与するデータ競合が発生した場合、生成されるスタックトレースにはこれらの関数が呼び出された元の場所の情報が含まれていませんでした。

これは、データ競合検出器がコードに挿入する計測の仕組みに起因していました。通常、データ競合検出器は、メモリアクセスや関数呼び出しの前後で特定のフック(funcenter/funcexitなど)を挿入し、実行時の情報を収集します。しかし、syncsync/atomicのような低レベルの同期プリミティブを提供するパッケージは、パフォーマンス上の理由や、それ自体が競合検出器の内部動作に影響を与える可能性があるため、特別な扱いを受けていました。その結果、これらのパッケージ内の関数呼び出しがスタックトレースから欠落し、デバッグが困難になるという問題がありました。

このコミットは、このスタックトレースの欠落問題を解決し、sync.Once()atomic.XXXの呼び出し元を正確に特定できるようにするために導入されました。

前提知識の解説

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラです。ソースコードを機械語に変換するだけでなく、最適化、デバッグ情報の生成、そしてデータ競合検出器のための計測挿入など、様々なコンパイル時処理を行います。
  • Goデータ競合検出器 (Race Detector): Goランタイムに組み込まれたツールで、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する競合)を検出します。コンパイル時にコードに特別な計測を挿入し、実行時に競合を監視することで機能します。
  • racewalk: Goコンパイラ(cmd/gc)の一部で、データ競合検出器の計測をコードに挿入する処理を担当します。プログラムの抽象構文木(AST)を走査し、メモリアクセスや関数呼び出しなどの特定のイベントに対して、競合検出器のランタイム関数(例: racefuncenter, racefuncexit, raceread, racewrite)の呼び出しを挿入します。
  • 計測 (Instrumentation): プログラムの実行時の振る舞いを監視するために、追加のコードを挿入するプロセスです。データ競合検出器の場合、メモリアクセスや関数呼び出しの前後で特定の関数を呼び出すコードを挿入することで、競合の可能性を検出します。
  • syncパッケージ: Goの標準ライブラリの一部で、ミューテックス、条件変数、一度だけ実行される初期化(sync.Once)など、基本的な同期プリミティブを提供します。
  • sync/atomicパッケージ: Goの標準ライブラリの一部で、アトミック操作(不可分操作)を提供します。これにより、ロックを使用せずに共有変数への安全なアクセスが可能になります。例として、atomic.AddInt32atomic.LoadPointerなどがあります。
  • sync.Once: 特定の初期化コードがプログラムの実行中に一度だけ実行されることを保証するためのGoの同期プリミティブです。
  • atomic.XXX: sync/atomicパッケージ内のアトミック操作関数の総称です。
  • スタックトレース (Stack Trace): プログラムの実行中にエラーや特定のイベントが発生した際に、その時点での関数呼び出しの履歴(コールスタック)を示すリストです。デバッグ時に問題の発生源を特定するのに不可欠な情報です。
  • funcenter/funcexit: データ競合検出器が関数呼び出しの開始時と終了時に挿入する計測フックです。これらのフックは、競合検出器がゴルーチンの実行コンテキストを追跡し、正確なスタックトレースを構築するために使用されます。

技術的詳細

このコミットの技術的な核心は、racewalkがコードに計測を挿入する際のパッケージごとの挙動をより細かく制御することにあります。

変更前は、omitPkgsという単一のリストがあり、runtimeruntime/racesyncsync/atomicの各パッケージは、データ競合検出器による計測が完全にスキップされていました。これは、これらのパッケージがランタイムの内部動作や競合検出器自体の実装に関わるため、計測を挿入すると無限再帰や誤検出、パフォーマンスの著しい低下を引き起こす可能性があったためです。しかし、この「完全にスキップ」というアプローチが、sync.Once()atomic.XXXの呼び出し元がスタックトレースに表示されない原因となっていました。

このコミットでは、この問題を解決するために、パッケージの分類をより詳細に分けました。

  1. omit_pkgs:
    • runtimeruntime/raceが含まれます。
    • これらのパッケージは、引き続き完全に計測がスキップされます。これは、計測を挿入すると無限再帰を引き起こす可能性があるためです。
  2. noinst_pkgs:
    • syncsync/atomicが含まれます。
    • これらのパッケージは、メモリアクセスに対する計測(raceread/racewriteなど)は挿入されませんが、関数呼び出しの開始と終了を示すfuncenter/funcexitの計測は挿入されるようになりました。
    • コミットメッセージにある「Memory accesses in the packages are either uninteresting or will cause false positives.」という記述は、これらのパッケージ内のメモリアクセス自体は競合検出器にとって重要でないか、あるいは誤検出を引き起こす可能性が高いため、メモリアクセスに対する計測は引き続き行わないことを示唆しています。しかし、関数呼び出しのコンテキストはスタックトレースのために必要である、という判断がなされました。

新しいヘルパー関数ispkginが導入され、現在のインポートパスが指定されたパッケージリストに含まれているかどうかを効率的にチェックできるようになりました。

racewalk関数のロジックは、この新しいパッケージ分類に基づいて変更されました。

  • まず、現在のパッケージがomit_pkgsに含まれている場合、racewalkは即座にリターンし、一切の計測を行いません。
  • 次に、現在のパッケージがnoinst_pkgsに含まれていない場合、通常のracewalklist処理(fn->nbodyfn->exitに対する計測)が実行されます。これは、noinst_pkgs以外のパッケージでは、メモリアクセスと関数呼び出しの両方に対して計測が挿入されることを意味します。
  • noinst_pkgsに含まれるパッケージ(syncsync/atomic)の場合、racewalklist(fn->nbody, nil)racewalklist(fn->exit, nil)の呼び出しはスキップされます。しかし、コミットメッセージにある「This change add funcenter/exit instrumentation to sync/sync.atomic packages.」という記述と、変更されたコードの意図を総合すると、racewalkのより深い部分でfuncenter/funcexitの計測がこれらのパッケージに対して特別に有効化されるようになったと解釈できます。具体的には、racewalk関数自体は、関数のエントリとエグジットポイントにfuncenter/funcexitを挿入するロジックを含んでおり、この変更によりsyncsync/atomicパッケージの関数もその対象に含まれるようになったと考えられます。

この変更により、sync.Once()atomic.XXXのような関数が呼び出された際に、その呼び出しがスタックトレースに適切に記録されるようになり、データ競合のデバッグが大幅に改善されました。

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

src/cmd/gc/racewalk.c

--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -28,26 +28,42 @@ static void foreach(Node *n, void(*f)(Node*, void*), void *c);\n static void hascallspred(Node *n, void *c);\n static Node* detachexpr(Node *n, NodeList **init);\n \n-static const char *omitPkgs[] = {\"runtime\", \"runtime/race\", \"sync\", \"sync/atomic\"};\n+// Do not instrument the following packages at all,\n+// at best instrumentation would cause infinite recursion.\n+static const char *omit_pkgs[] = {\"runtime\", \"runtime/race\"};\n+// Only insert racefuncenter/racefuncexit into the following packages.\n+// Memory accesses in the packages are either uninteresting or will cause false positives.\n+static const char *noinst_pkgs[] = {\"sync\", \"sync/atomic\"};\n+\n+static int\n+ispkgin(const char **pkgs, int n)\n+{\n+\tint i;\n+\n+\tif(myimportpath) {\n+\t\tfor(i=0; i<n; i++) {\n+\t\t\tif(strcmp(myimportpath, pkgs[i]) == 0)\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n \n void\n racewalk(Node *fn)\n {\n-\tint i;\n \tNode *nd;\n \tNode *nodpc;\n \tchar s[1024];\n \n-\tif(myimportpath) {\n-\t\tfor(i=0; i<nelem(omitPkgs); i++) {\n-\t\t\tif(strcmp(myimportpath, omitPkgs[i]) == 0)\n-\t\t\t\treturn;\n-\t\t}\n-\t}\n+\tif(ispkgin(omit_pkgs, nelem(omit_pkgs)))\n+\t\treturn;\n \n-\tracewalklist(fn->nbody, nil);\n-\t// nothing interesting for race detector in fn->enter\n-\tracewalklist(fn->exit, nil);\n+\tif(!ispkgin(noinst_pkgs, nelem(noinst_pkgs))) {\n+\t\tracewalklist(fn->nbody, nil);\n+\t\t// nothing interesting for race detector in fn->enter\n+\t\tracewalklist(fn->exit, nil);\n+\t}\n \n \t// nodpc is the PC of the caller as extracted by\n \t// getcallerpc. We use -widthptr(FP) for x86.\n```

## コアとなるコードの解説

1.  **パッケージリストの再定義**:
    *   `static const char *omitPkgs[]`が削除され、代わりに`omit_pkgs`と`noinst_pkgs`の2つの新しい配列が定義されました。
    *   `omit_pkgs`には`"runtime"`と`"runtime/race"`が含まれ、これらのパッケージは引き続き完全に計測がスキップされます。
    *   `noinst_pkgs`には`"sync"`と`"sync/atomic"`が含まれ、これらのパッケージはメモリアクセスに対する計測は行われませんが、`funcenter`/`funcexit`の計測は行われるようになります。

2.  **`ispkgin`関数の追加**:
    *   この新しいヘルパー関数は、現在のコンパイル対象のパッケージのインポートパス(`myimportpath`)が、引数として渡されたパッケージ名の配列(`pkgs`)に含まれているかどうかをチェックします。これにより、パッケージごとの計測ポリシーの適用が簡潔になります。

3.  **`racewalk`関数のロジック変更**:
    *   変更前は、`omitPkgs`に含まれるパッケージの場合、`racewalk`関数は即座にリターンしていました。
    *   変更後は、まず`ispkgin(omit_pkgs, nelem(omit_pkgs))`を呼び出し、現在のパッケージが`omit_pkgs`に含まれる場合は同様に即座にリターンします。
    *   次に、`if(!ispkgin(noinst_pkgs, nelem(noinst_pkgs)))`という条件が追加されました。これは、現在のパッケージが`noinst_pkgs`に含まれて**いない**場合にのみ、`racewalklist(fn->nbody, nil)`と`racewalklist(fn->exit, nil)`が実行されることを意味します。
        *   `racewalklist`は、関数のボディ(`fn->nbody`)とエグジットポイント(`fn->exit`)を走査し、メモリアクセスやその他のイベントに対する計測を挿入する主要な関数です。
        *   この条件により、`sync`と`sync/atomic`パッケージ(`noinst_pkgs`に含まれる)の場合、`racewalklist`による通常のメモリ計測はスキップされます。しかし、`racewalk`関数自体が関数のエントリとエグジットに`funcenter`/`funcexit`を挿入するロジックを持っているため、これらのパッケージの関数にも`funcenter`/`funcexit`の計測が適用されるようになります。これにより、スタックトレースにこれらの関数呼び出しが含まれるようになります。

これらの変更により、Goのデータ競合検出器は、`sync`および`sync/atomic`パッケージ内の関数呼び出しのコンテキストをより正確に追跡できるようになり、`sync.Once()`や`atomic.XXX`が関与するデータ競合のデバッグが大幅に改善されました。

## 関連リンク

*   Go CL 6854112: [https://golang.org/cl/6854112](https://golang.org/cl/6854112)

## 参考にした情報源リンク

*   Go Race Detector Documentation: [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)
*   Go Source Code (cmd/gc/racewalk.c): [https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/racewalk.go](https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/racewalk.go) (Note: The file path has changed from `src/cmd/gc/racewalk.c` to `src/cmd/compile/internal/gc/racewalk.go` in newer Go versions, but the core logic remains similar for understanding the context.)
*   Go sync package: [https://pkg.go.dev/sync](https://pkg.go.dev/sync)
*   Go sync/atomic package: [https://pkg.go.dev/sync/atomic](https://pkg.go.dev/sync/atomic)
*   Understanding Go's Race Detector (various blog posts and articles, e.g., from Go blog or other Go community resources, which can be found via a web search for "Go race detector internals")