问题概述
最新 main 上,NewLogHAKeeperClientWithRetry 在 HAKeeper 客户端创建失败后的 3 秒退避阶段使用不可取消的 time.Sleep。如果调用方 context 在退避期间被取消,函数不会立即返回,而是仍然等待完整 3 秒。
这与函数接收 context 并在外层检查 ctx.Done() 的语义不一致,也会延迟依赖该 helper 的服务启动取消和关闭。
验证基线
- 仓库:
matrixorigin/matrixone
- 分支:最新
main
- Commit:
824090c2e61351972cc16f866a57d52c7c2cab6e
- Commit 时间:
2026-07-17T01:10:07+00:00
- Commit 标题:
perf(hashmap): allocate int iterator buffers lazily (#25796)
- 测试范围:logservice/HAKeeper 客户端发现与重试;不涉及 2PC
现有测试暴露出的异常
仓库已有 Test_NewLogHAKeeperClientWithRetry 给 context 设置 1 秒超时,但没有断言返回耗时:
go test ./pkg/logservice \
-run '^Test_NewLogHAKeeperClientWithRetry$' \
-count=1 -v -timeout=30s
实际输出:
=== RUN Test_NewLogHAKeeperClientWithRetry
--- PASS: Test_NewLogHAKeeperClientWithRetry (3.00s)
PASS
也就是 context 在 1 秒时已经超时,函数却到 3 秒退避结束后才返回。
白盒测试过程
新增临时单测 TestNewLogHAKeeperClientWithRetryHonorsContextDuringBackoff:
- 用包内测试钩子
newHAKeeperClientFunc 注入稳定的连接失败。
- 等待第一次创建尝试已经发生,确保函数进入退避阶段。
- 立即取消调用方 context。
- 观察 200ms,期望 helper 返回
nil。
- 实际 200ms 内没有返回;必须等硬编码的 3 秒
Sleep 完成。
- 为避免测试制造 goroutine 泄漏,失败断言前继续等待该 goroutine自行退出,再恢复全局测试钩子。
核心断言:
<-attempted
cancel()
returnedPromptly := false
select {
case client := <-done:
returnedPromptly = true
require.Nil(t, client)
case <-time.After(200 * time.Millisecond):
}
// 清理:等待硬编码退避结束,避免遗留 goroutine
if !returnedPromptly {
client := <-done
require.Nil(t, client)
}
require.True(t, returnedPromptly,
"retry helper ignored context cancellation during backoff")
执行命令与结果
普通模式,重复 3 次
go test ./pkg/logservice \
-run '^TestNewLogHAKeeperClientWithRetryHonorsContextDuringBackoff$' \
-count=3 -v -timeout=20s
结果:FAIL 3/3,每轮都耗时约 3 秒:
--- FAIL: TestNewLogHAKeeperClientWithRetryHonorsContextDuringBackoff (3.00s)
Error: Should be true
Messages: retry helper ignored context cancellation during backoff
race 模式,重复 3 次
go test -race ./pkg/logservice \
-run '^TestNewLogHAKeeperClientWithRetryHonorsContextDuringBackoff$' \
-count=3 -v -timeout=25s
结果:FAIL 3/3,失败原因一致;未出现 race detector 告警。
根因分析
pkg/logservice/hakeeper_client.go 的重试循环只在每轮顶部检查 context:
select {
case <-ctx.Done():
return nil
default:
if err := createFn(); err != nil {
time.Sleep(time.Second * 3)
continue
}
return c
}
连接失败后直接进入 time.Sleep(3s)。退避期间没有任何 ctx.Done() 分支,因此取消只能等到下一轮循环才被观察到。
影响
- 服务启动、HAKeeper 发现或测试取消最多额外延迟约 3 秒。
- 上层即使给出更短 deadline,也无法约束这段退避等待。
- 故障或滚动启停场景中会放大节点停止和重试延迟。
建议修复
将不可取消的 time.Sleep 改为 context-aware timer,例如:
timer := time.NewTimer(3 * time.Second)
defer timer.Stop()
select {
case <-ctx.Done():
return nil
case <-timer.C:
}
并把本 issue 的白盒用例作为回归测试。
查重
已搜索 NewLogHAKeeperClientWithRetry context cancellation、HAKeeper client retry sleep context 以及精确函数名,未发现相同 issue。
问题概述
最新
main上,NewLogHAKeeperClientWithRetry在 HAKeeper 客户端创建失败后的 3 秒退避阶段使用不可取消的time.Sleep。如果调用方 context 在退避期间被取消,函数不会立即返回,而是仍然等待完整 3 秒。这与函数接收 context 并在外层检查
ctx.Done()的语义不一致,也会延迟依赖该 helper 的服务启动取消和关闭。验证基线
matrixorigin/matrixonemain824090c2e61351972cc16f866a57d52c7c2cab6e2026-07-17T01:10:07+00:00perf(hashmap): allocate int iterator buffers lazily (#25796)现有测试暴露出的异常
仓库已有
Test_NewLogHAKeeperClientWithRetry给 context 设置 1 秒超时,但没有断言返回耗时:实际输出:
也就是 context 在 1 秒时已经超时,函数却到 3 秒退避结束后才返回。
白盒测试过程
新增临时单测
TestNewLogHAKeeperClientWithRetryHonorsContextDuringBackoff:newHAKeeperClientFunc注入稳定的连接失败。nil。Sleep完成。核心断言:
执行命令与结果
普通模式,重复 3 次
结果:FAIL 3/3,每轮都耗时约 3 秒:
race 模式,重复 3 次
结果:FAIL 3/3,失败原因一致;未出现 race detector 告警。
根因分析
pkg/logservice/hakeeper_client.go的重试循环只在每轮顶部检查 context:连接失败后直接进入
time.Sleep(3s)。退避期间没有任何ctx.Done()分支,因此取消只能等到下一轮循环才被观察到。影响
建议修复
将不可取消的
time.Sleep改为 context-aware timer,例如:并把本 issue 的白盒用例作为回归测试。
查重
已搜索
NewLogHAKeeperClientWithRetry context cancellation、HAKeeper client retry sleep context以及精确函数名,未发现相同 issue。