Skip to content

Add SessionPool.CheckHealthAsync connectivity probe 新增 SessionPool.CheckHealthAsync 连通性探针 - #63

Open
CritasWang wants to merge 1 commit into
apache:mainfrom
CritasWang:feat/session-pool-health-check
Open

Add SessionPool.CheckHealthAsync connectivity probe 新增 SessionPool.CheckHealthAsync 连通性探针#63
CritasWang wants to merge 1 commit into
apache:mainfrom
CritasWang:feat/session-pool-health-check

Conversation

@CritasWang

@CritasWang CritasWang commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Addresses the third issue raised in #61 (the IsOpen() semantics discussion). Complements #62, which
fixes the two blocking defects; this PR adds the API that was deliberately left out of that one to keep it
focused on the bugs.

对应 #61 中提出的第三个议题(IsOpen() 语义讨论)。与 #62 互补:#62 修复两个导致阻塞的缺陷,本 PR 新增
当时为保持该 PR 聚焦于缺陷而刻意剥离的 API。

Why / 背景

IsOpen() is !_isClose, and _isClose only flips on explicit Open() / Close(). The client keeps no
heartbeat, so a server going down never changes it. That is working as designed and matches the Java
client — but the name reads like a health check, and users write guards such as:

IsOpen()!_isClose,只有显式 Open() / Close() 会改变它;客户端无心跳,服务端断开不会回写。
这是符合设计的(与 Java 客户端一致),但这个命名读起来像健康检查,于是用户会写出这样的守卫:

// Never re-opens: IsOpen() stays true even while every connection is dead
if (_pool != null && _pool.IsOpen()) return;

Rather than change IsOpen() — which would be a silent behavioural break for anyone relying on the
lifecycle meaning — this PR adds the connectivity check it is mistaken for.

与其修改 IsOpen()(会对依赖其生命周期语义的使用方造成无声的行为破坏),本 PR 选择新增它被误认为具备的
连通性检查能力。

What / 实现

SessionPool.CheckHealthAsync(CancellationToken) issues one lightweight request on an idle pooled
connection and returns a SessionPoolHealth snapshot (Status, AvailableClients, TotalPoolSize,
FailedReconnections, Message, Error).

SessionPool.CheckHealthAsync(CancellationToken) 在空闲连接上发送一次轻量请求,返回 SessionPoolHealth
快照(状态、可用连接数、池容量、重连失败次数、说明、底层异常)。

var health = await sessionPool.CheckHealthAsync();
if (!health.IsHealthy)
{
    logger.LogWarning("{Health}", health);   // "Unhealthy: The server did not answer the probe: ..."
}
SessionPoolHealthStatus Meaning / 含义
NotOpen Pool not opened, or already closed. No network call is attempted. 池未打开或已关闭,不发起网络调用
Healthy The server answered the probe. 服务端已响应探测
Degraded Open, but no idle connection to probe with. 已打开但无空闲连接可探测
Unhealthy A connection was available but the server did not answer. 有可用连接但服务端未响应

Design decisions worth reviewing / 需要评审关注的设计决策:

  • The probe never blocks. ConcurrentClientQueue.TryTake is added for this. A health endpoint must not
    queue behind application load, so when every client is busy the probe returns Degraded at once rather
    than waiting. This also keeps the probe independent of the pool wait timeout fixed in Fix SessionPool blocking indefinitely after a server outage 修复服务端断开后 SessionPool 永久阻塞的问题 #62.
    探针永不阻塞:为此新增 ConcurrentClientQueue.TryTake。健康检查端点不应排在业务负载之后,所以所有
    连接繁忙时立即返回 Degraded。这也使探针不依赖 Fix SessionPool blocking indefinitely after a server outage 修复服务端断开后 SessionPool 永久阻塞的问题 #62 中修复的池等待超时。
  • Degraded is deliberately not a server verdict. It only means nothing was idle at that instant.
    Treating saturation as an outage would make the check useless under load.
    Degraded 刻意不表示服务端故障,只表示当时没有空闲连接。把饱和当作故障会让该检查在高负载下失去意义。
  • The borrowed connection is always returned, and a failed probe does not close it. The existing
    reconnect-on-use path still handles the dead connection on the next real operation, so the probe has no
    side effects on pool state.
    借出的连接总会归还,探测失败也不关闭它:失效连接仍由既有的按需重连路径在下次真实操作时处理,探针
    对池状态无副作用。
  • No implicit timeout. Callers bound the probe with a CancellationToken if they want to; adding a
    hidden default felt worse than making it explicit. Happy to add one if reviewers disagree.
    不设隐式超时:调用方可用 CancellationToken 自行限时。相比隐藏的默认值,显式更好。若评审有不同
    意见,可以加上。

TableSessionPool forwards the method with identical semantics.
TableSessionPool 以相同语义转发该方法。

Relationship to #62 / 与 #62 的关系

#62 is now merged (178ebb0), and this branch has been rebased onto it. Because #62 was squash-merged, the
three commits this branch previously carried no longer matched upstream and GitHub reported a conflict; I
rebased with --onto upstream/main so only the health-check commit remains. This PR is now a single
commit against current main with no conflicts.

#62 已合并(178ebb0),本分支已 rebase 到其之上。由于 #62 采用 squash 合并,本分支原先携带的三个提交
与上游不再匹配,GitHub 因此报告冲突;我使用 --onto upstream/main 进行 rebase,只保留健康检查这一个提交。
本 PR 现在是基于当前 main 的单个提交,无冲突。

The overlaps with #62 were resolved during the earlier rebase and carried through:
#62 的重叠已在此前的 rebase 中解决并延续至今:

Tests / 测试

SessionPoolHealthTests covers the NotOpen path (asserting no network call is attempted and the call
returns promptly), the non-blocking TryTake semantics, and the SessionPoolHealth snapshot itself.

SessionPoolHealthTests 覆盖 NotOpen 路径(断言不发起网络调用且立即返回)、TryTake 的非阻塞语义,
以及 SessionPoolHealth 快照本身。

dotnet test tests/Apache.IoTDB.Tests — 29 passed, 0 failed. dotnet build Apache.IoTDB.sln — 0 errors.
dotnet format --verify-no-changes — clean.

The Healthy / Unhealthy transitions need a live server and are left to the e2e suite rather than
simulated with a mock transport.
Healthy / Unhealthy 的状态转换需要真实服务端,交由 e2e 套件覆盖,未用 mock transport 模拟。

Note: the unit tests were executed locally against net8.0 because no .NET 5 arm64 runtime is available on
this machine; the committed TargetFramework is unchanged at net5.0.
说明:本机没有 .NET 5 的 arm64 运行时,单元测试在本地以 net8.0 执行;提交的 TargetFramework 仍为 net5.0

Docs / 文档

  • docs/SessionPool_Exception_Handling.md — new section contrasting CheckHealthAsync with IsOpen, the
    status table, behaviour notes, and the existing health-check example rewritten to delegate to the
    built-in probe.
    docs/SessionPool_Exception_Handling.md —— 新增对比 CheckHealthAsyncIsOpen 的章节、状态表、
    行为说明,并将原有的健康检查示例改写为委托给内置探针。
  • docs/API.md — added the CheckHealthAsync row and clarified IsOpen.
    docs/API.md —— 新增 CheckHealthAsync 一行并澄清 IsOpen

@CritasWang
CritasWang force-pushed the feat/session-pool-health-check branch from e985874 to 6e27d52 Compare July 31, 2026 04:47
新增 SessionPool.CheckHealthAsync 连通性探针

IsOpen() is a lifecycle flag - it reports whether the caller has opened the pool
and not yet closed it. Because the client keeps no heartbeat, it stays true after
the server goes down, and users routinely mistake it for a health check, e.g.
`if (pool.IsOpen()) return;` in a lazy-open guard, which then never rebuilds the
pool. This adds the connectivity check that IsOpen() is mistaken for, without
changing IsOpen() itself.
IsOpen() 是生命周期标志,仅表示调用方是否已打开且尚未关闭连接池。由于客户端无心跳,
服务端断开后它仍为 true,用户普遍将其误当作健康检查(例如惰性打开守卫里的
`if (pool.IsOpen()) return;`),从而导致连接池永远不会被重建。本提交新增了
IsOpen() 被误认为具备的连通性检查能力,且不改变 IsOpen() 自身行为。

- Add SessionPool.CheckHealthAsync(CancellationToken), which issues one lightweight
  request on an idle pooled connection and returns a SessionPoolHealth snapshot
  carrying Status, AvailableClients, TotalPoolSize, FailedReconnections, Message
  and the underlying Error.
  新增 SessionPool.CheckHealthAsync(CancellationToken):在空闲连接上发送一次轻量请求,
  返回包含状态、可用连接数、池容量、重连失败次数、说明和底层异常的 SessionPoolHealth 快照。

- Add SessionPoolHealthStatus with four outcomes: NotOpen (no network call is made),
  Healthy, Degraded (open but no idle connection to probe with - a saturation signal,
  not a server verdict) and Unhealthy (a connection was available but the server did
  not answer).
  新增 SessionPoolHealthStatus 四种结果:NotOpen(不发起网络调用)、Healthy、
  Degraded(池已打开但无空闲连接可用于探测,表示饱和而非服务端故障)、
  Unhealthy(有可用连接但服务端未响应)。

- Add ConcurrentClientQueue.TryTake so the probe never blocks. A health endpoint
  must not queue behind application load, so when every client is busy the probe
  returns Degraded immediately instead of waiting.
  新增 ConcurrentClientQueue.TryTake 使探针永不阻塞:健康检查端点不应排在业务负载之后,
  因此所有连接繁忙时立即返回 Degraded 而不是等待。

- The borrowed connection is always returned to the pool and a failed probe does not
  close it, so the existing reconnect-on-use path is unchanged.
  借出的连接总会归还池中,探测失败也不会关闭它,因此既有的按需重连路径保持不变。

- Forward CheckHealthAsync from TableSessionPool with identical semantics.
  TableSessionPool 以相同语义转发 CheckHealthAsync。

- Add tests for the NotOpen path, non-blocking TryTake semantics and the health
  snapshot; document the API in docs/SessionPool_Exception_Handling.md and docs/API.md.
  补充 NotOpen 路径、TryTake 非阻塞语义与健康快照的测试;在相关文档中说明该 API。
@CritasWang
CritasWang force-pushed the feat/session-pool-health-check branch from 6e27d52 to 03b3bf7 Compare July 31, 2026 10:22
@CritasWang

Copy link
Copy Markdown
Contributor Author

Rebased onto main now that #62 is merged (178ebb0).

#62 was squash-merged, so the three commits this branch previously carried no longer matched upstream and GitHub started reporting a conflict. I rebased with --onto upstream/main to drop them, leaving only the health-check commit — this PR is now a single commit (03b3bf7) against current main, with no conflicts and nothing from #62 in the diff.

Verified locally against merged main: dotnet build Apache.IoTDB.sln 0 errors, 44 unit tests passing, dotnet format --verify-no-changes clean.

Ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant