Geekertao-bot(openclaw) and Geekertao-bot
29470bb1c7
test: fix limiter benchmark priming state and comparison basis ( #3 )
...
* test: fix limiter benchmark priming state and comparison basis
上一提交(8d6781e)的基准注释与实际行为不符:新实现经公开入口
(allow/allowAt)预填 limit+1 次时,第 limit+1 次调用**已被拒绝、不会追加**,
因此实际只保留 limit 条记录(实测 100),而旧实现侧直接写入 limit+1 条
(实测 101)。原注释却写成两边同为 limit+1、声称「记录数一致(101)」,
属错误陈述。
修正方式(不做伪统一,改为如实描述并各自准备稳态):
- 不再声称两边记录数相同。两个实现的拒绝条件不同(旧 len > limit,
新 len >= limit),可长期停留的饱和记录数天然相差 1(101 vs 100),
这正是本 PR 修正的边界偏差,不可也不应被「统一」掉:
新实现无法经公开入口达到 limit+1 条(运行期不可达状态),旧实现若只预填
limit 条则第 1 次调用会先放行追加,使计时区间混入路径不同的样本。
- 改为让每侧各自准备在自己的饱和拒绝态,使计时区间内每次调用都是同一条
「已饱和 → 拒绝」路径;各自的记录条数由 prime 函数返回并被断言。
- 新增 TestBenchmarkPrimeStates:断言新实现饱和态 = limit 条、旧实现 = limit+1 条,
且饱和后调用被拒绝、记录数不再增长,防止注释再次与代码脱节。
- 修正 NewRealClock 系列:原实现未预置,计时区间混入前 limit 次放行调用;
现同样预置到饱和态(真实时钟),并说明其时钟口径不可与注入固定时间的
New/Old 两组混比。
- 记录一个踩坑:b.ResetTimer() 只归零累计耗时、**不会启动**计时器,
b.StopTimer() 之后必须显式 b.StartTimer(),否则计时区间恒为 0、
框架会把 b.N 放大到上限(实测卡在 1e9)。已在代码内注明。
其余(sweep 初始化移出计时区间 + SweepWithFill 端到端口径、并行基准 worker
起始偏移分散)沿用上一提交,未改动实现代码 main.go 与其它测试。
* test: fill 10 records per IP in sweep fixture and state timing scope
fixSweepLimiter() 的注释与 PR 描述声称「每个 IP 10 条记录」,但代码里每个 IP
只调用一次 l.allowAt(ip, 10, base),实际只写入 1 条记录,导致回收基准测的是
「5000 个 IP × 1 条」而非声称的「5000 个 IP × 10 条」,与 ns/op 的可解释性
一并失真。
修正:
- fixSweepLimiter() 改为经限流器正常入口(allowAt)对每个 IP 调用 sweepLimit
(10) 次,使每个 IP 恰好写入 10 条记录;并在填充后自检每个 IP 的记录数,
防止注释再次与代码脱节。
- 新增常量 sweepLimit / sweepRecordsPerIP / sweepIPCount,消除散落的魔数。
- 明确两者的计时范围:BenchmarkLimiterSweep **排除**填充成本(StopTimer 期间
填充,仅计 sweepLocked 与其触发它的那一次 allow),BenchmarkLimiterSweepWithFill
**有意计入**填充成本,故其结果不得当作纯回收耗时。
- 新增 TestSweepFixtureRecordsAllExpired:断言夹具每个 IP 恰好 10 条记录、且这
10 条在触发回收时全部早于 cutoff(全部过期),并且一次 sweep 后仅剩新 IP。
工作量随之从 5,000 条变为 50,000 条记录,Sweep 的 ns/op 上升属预期(测的东西
变多了),不是性能回归。
仅改 bench_test.go;未改 main.go、其它测试、go.mod/go.sum、CI、Dockerfile。
* test: fix sweep benchmark timing scope note and per-iteration trigger time
GPT 复核剩余两点,均已修正(仅 bench_test.go):
1) fixSweepLimiter 的注释说「填充工作不计入任何计时区间:调用方需在 StopTimer
状态下调用它」,与 BenchmarkLimiterSweepWithFill 有意把填充计入计时相矛盾。
改为如实描述:填充成本是否计入**由调用方决定**,并分别点名两个基准的做法。
2) BenchmarkLimiterSweep / SweepWithFill 把 base := time.Now() 放在循环外,且固定
使用 base.Add(2*time.Minute)。这是真实隐患:newRateLimiter 会把 lastSweep 设为
当时的真实 time.Now(),而 sweep 的触发条件是 now.Sub(l.lastSweep) >= l.sweepEvery
(sweepEvery 已置 0,即要求触发时刻**不早于** lastSweep)。一旦基准循环的墙钟时间
超过 2 分钟,循环外的 next 就会早于新一轮限流器的 lastSweep,差值变负 → 回收
**静默不触发**,基准实际退化为在测一条空的拒绝路径(且不报错)。
改为每轮在循环内取 base 并相对本轮填充时刻计算 next,使触发条件恒成立。
已核对:此前的数据并非无效(单次基准循环墙钟约 17~25s,未越过 2 分钟),
但代码是潜在错误的,现已消除。
3) 新增 TestSweepTriggersWithStaleBase 把该陷阱固定下来:用远早于现在的 stale base
构造夹具、以 staleNext 触发时,断言回收**没有**发生(保留 64+1 个 IP);改用相对
当前时刻的触发时刻后,断言回收完成(仅剩 1 个 IP)。
未改 main.go、其它测试、go.mod/go.sum、CI、Dockerfile。
---------
Co-authored-by: Geekertao-bot <254965144+Geekertao-bot@users.noreply.github.com >
2026-10-11 12:19:41 +08:00