diff --git a/internal/runtime/memory/expire.go b/internal/runtime/memory/expire.go index b0e0468..b7731ef 100644 --- a/internal/runtime/memory/expire.go +++ b/internal/runtime/memory/expire.go @@ -47,8 +47,9 @@ const ( // expireSweepBatchSize 是单批取行 / 删除的行数上限,maxExpireSweepBatches 是 // 一轮清扫的批数上限。两者一起把「一轮清扫」的时长、单次内存占用与**连接池 - // 占用**(本项目的 SQLite 池是单连接)都变得有界 —— 照 `preinsert_gc.go:65-69` - // 的批删范式(`design/23 §19.3-3`「一批删除有条数 / 批数上限」)。 + // 占用**(本项目的 SQLite 池是单连接)都变得有界 —— 照 `preinsert_gc.go` 里 + // `ingestReviewSweepBatchSize` / `maxIngestReviewSweepBatches` 那一对常量的 + // 批删范式(`design/23 §19.3-3`「一批删除有条数 / 批数上限」)。 // 上限之积 = 一轮最多删 1 万行;积压时剩下的留给下一轮,不一次 DELETE 全表。 expireSweepBatchSize = 500 maxExpireSweepBatches = 20 diff --git a/internal/runtime/memory/tidy_service.go b/internal/runtime/memory/tidy_service.go index cd31663..ee9cb31 100644 --- a/internal/runtime/memory/tidy_service.go +++ b/internal/runtime/memory/tidy_service.go @@ -141,11 +141,13 @@ func (s *TidyService) Start(ctx context.Context) { // 或「Wait 漏掉一笔尚未 Add 的 goroutine(那会当场泄漏一个清扫 goroutine)」 // 这两种窗口。 // - // ⚠️ 这把锁**不**覆盖 `Stop` 的 `wg.Wait`:`Stop` 在 `tidy_service.go:214` - // 就放锁,到 `:219`(另一个 goroutine 里)才 Wait。因此 WaitGroup 的复用纪律 - // (「上一轮 Wait 返回前不得再 Add」)**不是**由这把锁给的,而是靠 - // 「`Start` / `Stop` 在整个进程生命周期各只调用一次」维持:`Start` 只在 - // `cmd/cyrene/main.go:97` 调、`Stop` 只在 `cmd/cyrene/wire_services.go:212` 调。 + // ⚠️ 这把锁**不**覆盖 `Stop` 的 `wg.Wait`:在同一个 `func (s *TidyService) Stop` + // 里,`s.mu.Unlock()` 在前、`s.wg.Wait()`(另一个 goroutine 里)在后,两者之间 + // 没有锁。因此 WaitGroup 的复用纪律(「上一轮 Wait 返回前不得再 Add」)**不是** + // 由这把锁给的,而是靠「`Start` / `Stop` 在整个进程生命周期各只调用一次」维持: + // `Start` 只在组合根启动期调一次(`cmd/cyrene/main.go` 的 + // `a.memoryTidy.Start(ctx)`)、`Stop` 只在关停链里调一次(`cmd/cyrene/wire_services.go` + // 的 `_ = a.memoryTidy.Stop(ctx)`)。 // 交叉调用(`Start` 与在飞的 `Stop` 并发)在 `-race` 下会 `panic: sync: // WaitGroup is reused before previous Wait has returned` —— 实测该 panic 在 // M3 之前的基线上同样可复现(既有性质,非本切片引入),只是当前调用点**不可达**。