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
@@ -227,14 +227,17 @@ func tuningUnitLabel(spec memoryTidyTuningSpec) string {
|
||||
}
|
||||
}
|
||||
|
||||
// tuningValueClipRunes 是错误文案里回显「收到的值」的 rune 上限。
|
||||
const tuningValueClipRunes = 40
|
||||
|
||||
// clipJSON 把收到的原始 JSON 片段截断成可安全回显的短串(错值可能很长)。
|
||||
//
|
||||
// 🔴 按 **rune** 截断,**不按字节**:值里完全可能是中文(主人就爱填中文),按字节切
|
||||
// 会把一个多字节字符切成两半 —— 回显里因此出现半截 UTF-8(JSON 编码后变成 U+FFFD
|
||||
// 替换符),而这条文案的用途正是让人**看清自己填错了什么**。手法与同包的
|
||||
// `clipRunes`(notifications.go)同源,直接复用而不是再写一份。
|
||||
func clipJSON(raw json.RawMessage) string {
|
||||
const maxClip = 40
|
||||
s := strings.TrimSpace(string(raw))
|
||||
if len(s) <= maxClip {
|
||||
return s
|
||||
}
|
||||
return s[:maxClip] + "…"
|
||||
return clipRunes(strings.TrimSpace(string(raw)), tuningValueClipRunes)
|
||||
}
|
||||
|
||||
// isJSONNull 报告这段原始值是不是 JSON 的 `null`(清除哨兵)。
|
||||
|
||||
Reference in new issue
Block a user