From a30bbf756766246a96e347df2e7dbf8efcab6ef5 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:41:38 +0900 Subject: [PATCH] docs: revise G-1 - ring buffer pointer approach avoids data race safely Store *LogEntry in ring buffer instead of LogEntry values. add() writes new pointer per slot (1 alloc per log write, acceptable). recent() copies only pointers; pointed-to objects are never overwritten so callers hold valid read-only views after lock release. Update Phase 1 instruction accordingly. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 69b98dae7..51db88659 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -648,24 +648,48 @@ F-1 ✅ F-2 ✅ F-3 ✅ F-4 ✅ F-5 ✅ F-6 ✅ F-7 ✅ F-8 ✅ — 全8件が --- -### G-1. Phase 1: `recent()` → `[]*LogEntry` はデータレースになる 🚨 +### G-1. Phase 1: `recent()` → `[]*LogEntry` の実現方法を修正 + +**当初の指摘**: `entries []LogEntry` のまま `[]*LogEntry` を返すとデータレース +**訂正**: リングバッファの内部型を `[]*LogEntry` に変えることで、安全にポインタを返せる。 + +``` +現状: entries [ LogEntry | LogEntry | LogEntry ] + add() → スロットの値を上書き → 返したポインタ先が破壊される (危険) + +変更後: entries [ *LogEntry | *LogEntry | *LogEntry ] + add() → スロットのポインタを差し替えるだけ + 古い *LogEntry オブジェクト自体は誰も上書きしない + recent() → ポインタだけコピーして返す (ロック解放後も安全) +``` ```go -// logger.go (現状 — 正しい) -func (rb *logRingBuffer) recent(limit int) []LogEntry { +// 変更後のイメージ +type logRingBuffer struct { + entries []*LogEntry // ポインタを保持 + ... +} +func (rb *logRingBuffer) add(e LogEntry) { + rb.mu.Lock() + rb.entries[rb.head] = &e // 新規アロケーション、スロットはポインタ差し替え + ... +} +func (rb *logRingBuffer) recent(limit int) []*LogEntry { rb.mu.RLock() defer rb.mu.RUnlock() - result := make([]LogEntry, n) + result := make([]*LogEntry, n) for i := 0; i < n; i++ { - result[i] = rb.entries[...] // ← RLock 中に値コピー + result[i] = rb.entries[...] // ポインタのコピーのみ、LogEntry 構造体のコピーなし } - return result // 返した後は rb.entries が上書きされても安全 + return result // 指し先は不変 → ロック解放後も安全に読める } ``` -`rb.entries` はリングバッファの内部配列で、`add()` が呼ばれると古いエントリが上書きされる。現在の実装は RLock 中に値コピーして返すため安全。 +**トレードオフ**: `add()` 毎に1ヒープアロケーション増加。ただしログ書き込みはすでにI/Oを伴うパスなので許容範囲。`recent()` 側では LogEntry 構造体のコピーが不要になり、頻繁な読み取り(Mini App ストリーム等)でアロケーション削減の恩恵が出る。 -`[]*LogEntry` に変更するとリングバッファ内部へのポインタを返すことになる。呼び出し側がそのポインタを保持している間に新しいログが `add()` されると、ポインタ先が上書きされてデータレースが発生する。この変更は**正確性を壊す**。Phase 1 から削除すること。 +Go には言語レベルの readonly 参照がないため「書き込み禁止の窓」は慣習的な保証になるが、LogEntry を logger パッケージ内でのみ生成・変更する設計であれば実質的に安全。 + +Phase 1 の変更内容を「`entries` の型を `[]*LogEntry` に変更 + `recent()` の戻り値を `[]*LogEntry` に変更」に更新すること。 ### G-2. Phase 3-1: `FormatPlanDisplay()` が修正対象に含まれていない