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:
1 parent
a017ab6cf8
commit
44c25eb03b
5 files changed
+73
-81
No files matched your search
@@ -2,7 +2,6 @@ package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -13,51 +12,17 @@ import (
|
||||
// 审查同一单写者纪律,**不新开并发写口**、不新开监听端口、不加任何 `.env` 键
|
||||
// (`.env` 已冻结,`design/13 §2.1`),也不碰会话 / 历史读数。
|
||||
//
|
||||
// 🔴 **挂在哪个 ctx 上**:清扫与整理**共用同一条生命周期** —— 它的 ctx 就是整理
|
||||
// 心跳的 ctx,它的 goroutine 登记进 TidyService 的 `wg`。因此
|
||||
// `cmd/cyrene/wire_services.go` 关闭链里的 `a.memoryTidy.Stop(ctx)` 会一并取消它
|
||||
// 并等它退出(§19.3-3 的「可停」),不必再为清扫加一条关闭路径、也不必改
|
||||
// `tidy_service.go`(那个文件归别的切片)。
|
||||
// 🔴 **挂点**:清扫与整理**同生共死** —— 它的 ctx 是 TidyService.Start 里建的那个
|
||||
// cctx(就是 `go s.loop(cctx)` 用的同一个),它的 goroutine 登记进同一个 `s.wg`。
|
||||
// 因此 `cmd/cyrene/wire_services.go` 关闭链里的 `a.memoryTidy.Stop(ctx)` 会一并
|
||||
// 取消它并等它退出(§19.3-3 的「可停」),不必再为清扫加一条关闭路径。
|
||||
//
|
||||
// 挂点用的是 TidyService 已有的注入缝 `wait`(心跳的等待原语,见
|
||||
// tidy_service.go:64-66):它由心跳 goroutine 以**心跳的 cctx** 调用,因此「第一次
|
||||
// wait 被调用」既是「Start 已经跑起来、ctx 已经建好」的确定信号,又恰好把清扫要的
|
||||
// 那个可取消 ctx 递到手上。清扫随后跑在**自己的** goroutine 与**自己的**小时级
|
||||
// ticker 上,不借用整理的调度节奏(整理的周期是 24h 档,不能拿来当清扫周期)。
|
||||
|
||||
// AttachExpirySweep 把到期清扫挂到整理服务的生命周期上。它由组合根在装配期调用
|
||||
// (`cmd/cyrene/wire_memory_tidy.go`),每个服务实例调用一次。
|
||||
//
|
||||
// 它在**第一次心跳等待**时启动清扫循环:此时 `Start` 已经建好可取消的 ctx,
|
||||
// 而那个 ctx 就是心跳 goroutine 手里这一个。ctx 每次变化(Start → Stop → Start)
|
||||
// 都会重新挂一个循环,因此「重启一次就永久失去清扫」不会发生。
|
||||
//
|
||||
// 它不启动任何 goroutine 直到那一次等待发生:整理被配置关掉(`Start` 直接返回)
|
||||
// 时,清扫也不会跑 —— 这与 `preinsert_gc.go` 的清扫随审查服务启停是同一条口径
|
||||
// (后台件随它的宿主启停,而不是凭空常驻)。
|
||||
func (s *TidyService) AttachExpirySweep() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
inner := s.wait
|
||||
if inner == nil {
|
||||
inner = waitTidyTimer
|
||||
}
|
||||
var (
|
||||
mu sync.Mutex
|
||||
attached context.Context
|
||||
)
|
||||
s.wait = func(ctx context.Context, d time.Duration) bool {
|
||||
mu.Lock()
|
||||
if attached != ctx {
|
||||
attached = ctx
|
||||
s.wg.Add(1)
|
||||
go s.expirySweepLoop(ctx)
|
||||
}
|
||||
mu.Unlock()
|
||||
return inner(ctx, d)
|
||||
}
|
||||
}
|
||||
// 🔴 挂点**必须**是 `Start` 里那处显式的 `go s.expirySweepLoop(cctx)`,不许改挂到
|
||||
// 某个注入缝上(例如包一层 `s.wait` 这个「心跳等待原语」)。理由是**可证性**:
|
||||
// 包注入缝意味着清扫的存活依赖于「`loop` 每一轮都调 `s.wait`」这个**没有任何契约
|
||||
// 固定**的假设 —— 将来谁换了等待原语、或把循环改成 `select` + 自己的 timer,清扫
|
||||
// 会**静默消失**(不报错、不红测试,只是临时期限再也删不掉)。起在 `Start` 里,
|
||||
// 读者一眼就能看到两个后台件同生共死。
|
||||
|
||||
// expirySweepLoop 是清扫的常驻循环:启动期先扫一次(清掉上次退出后到期的行),
|
||||
// 此后按 DefaultExpireSweepInterval 扫一次。它随 ctx 取消退出(Stop 会 join 它)。
|
||||
|
||||
Reference in new issue
Block a user