Skip to content

[Bug]: NewLogHAKeeperClientWithRetry ignores context cancellation during retry backoff #25828

Description

@Ariznawlll

问题概述

最新 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

  1. 用包内测试钩子 newHAKeeperClientFunc 注入稳定的连接失败。
  2. 等待第一次创建尝试已经发生,确保函数进入退避阶段。
  3. 立即取消调用方 context。
  4. 观察 200ms,期望 helper 返回 nil
  5. 实际 200ms 内没有返回;必须等硬编码的 3 秒 Sleep 完成。
  6. 为避免测试制造 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 cancellationHAKeeper client retry sleep context 以及精确函数名,未发现相同 issue。

Metadata

Metadata

Assignees

Labels

kind/bugSomething isn't workingseverity/s0Active / top priority for current sprint. Owner has committed to working on it now.

Type

Fields

No fields configured for Bug.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions