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 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-24 13:41:38 +09:00
parent fcc2fd2bf3
commit a30bbf7567

View file

@ -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()` が修正対象に含まれていない