refactor(memory): 到期清扫挂点从 wait 注入缝改为 TidyService.Start

塔裁定:清扫不许挂在注入缝上 —— 包一层 s.wait 会让「清扫是否存活」依赖于
「loop 每一轮都调 s.wait」这个**没有任何契约固定**的假设;将来谁换了等待原语、
或把循环改成 select + 自己的 timer,清扫会**静默消失**(不报错、不红测试,
只是临时期限再也删不掉)。后台件起在 Start 里,读者一眼看到它们同生共死。

- tidy_service.go:Start 里紧跟 go s.loop(cctx) 起 go s.expirySweepLoop(cctx),
  wg 计数与既有那一笔同处(同一把锁内加,避免 Add 与 Stop 的 wg.Wait 相撞)。
  `if !s.cfg.Enabled { … return }` 仍在其前 ⇒ 语义与改动前逐字相同:整理被关掉
  ⇒ 清扫不跑(与 preinsert_gc 随审查服务启停同口径)。
- expire_loop.go:删掉 AttachExpirySweep 与对 s.wait 的包裹;文件头写明
  「挂点必须是 Start 里那处显式的 go」,防止后人改回注入缝。
- expire_loop_test.go:那条运行时对照改写成 TestTidyServiceStart_清扫随服务起停
  —— 真 Start、真 Stop(Stop 不超时即证明清扫随 ctx 退出且登记在同一个 wg)。
- wire_memory_tidy.go:去掉组合根的挂载调用,注释改成「起在 Start 里,组合根零接线」。
- wire_memory_tidy_test.go:把 go s.expirySweepLoop(cctx) 钉进 TestMemoryTidyIsWired
  的 seams(摘掉那一行 ⇒ 用例红,已实测)。
This commit is contained in:
AskaEth committed 2026-10-13 21:43:27 +08:00
1 parent a017ab6cf8
commit 44c25eb03b
5 files changed
+73 -81

No files matched your search

+15
View File
@@ -111,6 +111,11 @@ func (s *TidyService) Enabled() bool { return s.cfg.Enabled }
// Start 启动心跳。它是幂等的:第二次调用是空操作。配置关闭时只记一条日志
// 并立即返回(关闭是显式选择,绝不静默)。ctx(可为 nil)是所有内部工作的
// 父级:取消它会像 Stop 一样停止服务。
//
// 它**同时**起临时期限的到期清扫(design/23 §19 的第 ② / ③ 步,见
// expire_loop.go):清扫与心跳共用同一个 cctx 与 wg,因此它跟整理同生共死 ——
// 整理被配置关掉时清扫也不跑(与 `preinsert_gc.go` 的清扫随审查服务启停同口径),
// `Stop` 会一并取消并 join 它。
func (s *TidyService) Start(ctx context.Context) {
if !s.cfg.Enabled {
s.log.Info("memory tidy disabled by configuration", "env", "MEMORY_TIDY_ENABLED")
@@ -128,12 +133,22 @@ func (s *TidyService) Start(ctx context.Context) {
cctx, cancel := context.WithCancel(ctx)
s.cancel = cancel
s.wg.Add(1)
// 到期清扫(design/23 §19 的第 ② / ③ 步)与心跳**同生共死**:同一个 cctx、
// 同一个 wg,因此 Stop 会一并取消并 join 它(§19.3-3 的「可停」)。计数在
// 这把锁里加(与上面那一笔同处)是刻意的:`Stop` 也持这把锁,这样 Add 与
// Stop 的 `wg.Wait` 不可能撞在一起。
s.wg.Add(1)
s.mu.Unlock()
// 持久事实先读进来:判定「要不要补跑」只看它,不看进程内状态。
s.loadPersistedLastRun(ctx)
// 两个后台件在这里一起起:整理心跳,以及临时期限的到期清扫
// (expire_loop.go)。清扫跑在**自己的** goroutine 与**自己的**小时级
// ticker 上 —— 整理的周期是 24h 档(intervalSec),不能拿来当清扫周期
// (§19.1「到期后 ≤ 一个清扫周期,默认每小时」)。
go s.loop(cctx)
go s.expirySweepLoop(cctx)
s.log.Info("memory tidy started",
"interval", s.cfg.Interval.String(),