fix(memory): 按字段扫清 s.cfg 的其余无锁读(P1)+ 回显文案按 rune 截断
review P1:applyTidyConfig 在运行期**整体覆写** s.cfg,而我只把调度读收进了持锁 访问器 —— 同一字段还有几处无锁读,其中两处是**活端点**: - TidyService.DryRun 的开关判定(在取 runMu **之前**)⇒ POST …/tidy/dry-run 与 ?async=1 那条会起后台 goroutine 的版本(真机 90s 级); - TidyService.ManualRun 的开关判定(同样在 runMu 之前)⇒ POST …/tidy/run; - (原本也漏的)Enabled() 与 Start 的启动日志块。 按**字段**重扫了一遍(不按文件):现在生产代码里对 s.cfg 的读**只剩**三个持锁访问器 内部(enabled / cfgSnapshot / scheduleSnapshot),写点只有 applyTidyConfig 一处; s.tidier.cfg 的读点全在持 runMu 的轮次 / 试算路径上(含异步版经 DryRun)。 新增第三条并发回归哨兵:真并发跑「试算 + 手动真跑」两个活端点 × 面板保存。 修前红/修后绿:撤回 DryRun 的同步 ⇒ 报「写 applyTidyConfig × 读 DryRun」;撤回 ManualRun 的同步 ⇒ 报「写 applyTidyConfig × 读 ManualRun」;两次变异都逐字还原 (diff 0 差异)。 顺手两条(塔指定): - 那条并发哨兵的 doc 与实现相反(还写着「正确顺序是 Stop → 写 → Start」),已改成 唤醒机制下的说法 —— 与「注释与实现不符」同类,不留旧说法。 - 非法值的回显文案原按**字节**截断(s[:40]),中文会被切成半截(非法 UTF-8,经 JSON 编码变成 U+FFFD 替换符)。改成按 **rune** 截断,直接复用同包既有的 clipRunes;用例取值刻意让截断点落在多字节字符中间(JSON 引号 + 一个 ASCII 前缀 错开对齐),并做过变异复验:改回按字节 ⇒ 用例红(切出半个汉字)。
This commit is contained in:
1 parent
97c0c1b94a
commit
4df8d6f53a
6 files changed
+167
-29
No files matched your search
@@ -122,7 +122,26 @@ func waitTidyTimer(ctx context.Context, d time.Duration) bool {
|
||||
func (s *TidyService) SessionID() string { return TidySessionID }
|
||||
|
||||
// Enabled 报告整理是否被配置开启(design/23 §4 的 MEMORY_TIDY_ENABLED)。
|
||||
func (s *TidyService) Enabled() bool { return s.cfg.Enabled }
|
||||
//
|
||||
// 它走 `enabled()` 而不是直接读字段:`Enabled` 是 `s.cfg` 里的一个字段,而面板调优
|
||||
// 能在**运行期**整体覆写 `s.cfg`(见 tidy_service_tuning.go 的 ApplyPanelTuning),
|
||||
// 因此该字段的**每一个**读点都必须与写方同步(`s.mu`)。
|
||||
func (s *TidyService) Enabled() bool { return s.enabled() }
|
||||
|
||||
// enabled 在读锁下报告整理开关。它是 `s.cfg.Enabled` 的**唯一**安全读法
|
||||
// (口径见 scheduleSnapshot 与上面 Enabled 的注释)。
|
||||
func (s *TidyService) enabled() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.cfg.Enabled
|
||||
}
|
||||
|
||||
// cfgSnapshot 在读锁下取一份配置快照:凡是不需要 lastRun 的读点用它。
|
||||
func (s *TidyService) cfgSnapshot() TidyConfig {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.cfg
|
||||
}
|
||||
|
||||
// Start 启动心跳。它是幂等的:第二次调用是空操作。配置关闭时只记一条日志
|
||||
// 并立即返回(关闭是显式选择,绝不静默)。ctx(可为 nil)是所有内部工作的
|
||||
@@ -133,7 +152,7 @@ func (s *TidyService) Enabled() bool { return s.cfg.Enabled }
|
||||
// 整理被配置关掉时清扫也不跑(与 `preinsert_gc.go` 的清扫随审查服务启停同口径),
|
||||
// `Stop` 会一并取消并 join 它。
|
||||
func (s *TidyService) Start(ctx context.Context) {
|
||||
if !s.cfg.Enabled {
|
||||
if !s.enabled() {
|
||||
s.log.Info("memory tidy disabled by configuration", "env", "MEMORY_TIDY_ENABLED")
|
||||
return
|
||||
}
|
||||
@@ -180,16 +199,18 @@ func (s *TidyService) Start(ctx context.Context) {
|
||||
go s.loop(cctx)
|
||||
go s.expirySweepLoop(cctx)
|
||||
|
||||
// 启动日志逐项取一次快照(面板调优能在运行期覆写 cfg,因此这些字段也不能裸读)。
|
||||
cfg := s.cfgSnapshot()
|
||||
s.log.Info("memory tidy started",
|
||||
"interval", s.cfg.Interval.String(),
|
||||
"quiet_interval", s.cfg.QuietInterval.String(),
|
||||
"quiet_hours", fmt.Sprintf("%02d:00-%02d:00", s.cfg.QuietStartHour, s.cfg.QuietEndHour),
|
||||
"recall_threshold", s.cfg.RecallThreshold,
|
||||
"scan_limit", s.cfg.ScanLimit,
|
||||
"compare_limit", s.cfg.CompareLimit,
|
||||
"candidate_limit", s.cfg.CandidateLimit,
|
||||
"auto_apply", s.cfg.autoApplyEnabled(),
|
||||
"auto_apply_per_round", s.cfg.AutoApplyPerRound,
|
||||
"interval", cfg.Interval.String(),
|
||||
"quiet_interval", cfg.QuietInterval.String(),
|
||||
"quiet_hours", fmt.Sprintf("%02d:00-%02d:00", cfg.QuietStartHour, cfg.QuietEndHour),
|
||||
"recall_threshold", cfg.RecallThreshold,
|
||||
"scan_limit", cfg.ScanLimit,
|
||||
"compare_limit", cfg.CompareLimit,
|
||||
"candidate_limit", cfg.CandidateLimit,
|
||||
"auto_apply", cfg.autoApplyEnabled(),
|
||||
"auto_apply_per_round", cfg.AutoApplyPerRound,
|
||||
"rule_version", TidyRuleVersion,
|
||||
"session_id", TidySessionID,
|
||||
"last_run_at", s.lastRunLabel())
|
||||
@@ -277,7 +298,7 @@ func (s *TidyService) RunOnce(ctx context.Context) (TidyStats, error) {
|
||||
// 由 tidy_auto.go 直接落地,其余仍等人点(候选 apply / 按 run 批量 apply)。
|
||||
// 关掉自动档只需 `$DATA_DIR/config/memory-tidy.json` 的 `{"autoApply": false}`。
|
||||
func (s *TidyService) ManualRun(ctx context.Context) (TidyStats, error) {
|
||||
if !s.cfg.Enabled {
|
||||
if !s.enabled() {
|
||||
return TidyStats{}, ErrTidyDisabled
|
||||
}
|
||||
return s.runRound(ctx, "manual")
|
||||
|
||||
Reference in new issue
Block a user