DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
OnlineSIM:在线接收短信,虚拟号码租用

取消 Npgsql 连接却报 SSL handshake,检查被包装的 TaskCanceledException

旧版 Npgsql 在 TLS 握手时把取消包装成 NpgsqlException,外层取消分支可能失效。10.0.2 的正式实现让取消通过,并与证书及超时错误分开。

取消数据库连接时,应用预期捕获 OperationCanceledException,却收到:

Npgsql.NpgsqlException: Exception while performing SSL handshake
 ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.

若异常发生在 NpgsqlConnection.OpenAsync 的 TLS 握手阶段,且调用方的 CancellationToken 已经取消,要检查旧版 Npgsql 对取消异常的包装。不能仅凭外层的 SSL handshake 字样,就把它判成证书不可信或 TLS 协议不兼容。

从异常链确认取消发生在哪一步

TaskCanceledException 是 OperationCanceledException 的子类。旧实现用较宽的 catch 包装握手失败,把它也包进 NpgsqlException,导致应用外层只捕获取消异常的分支没有命中。

这与“所有握手失败都属于取消”完全不同。先保留完整异常链、调用阶段、实际 Npgsql 包版本和传入 token 的状态。证书验证错误、连接超时、远端断连都可能经过同一握手函数,但不应统一转成用户取消。

通过 EF Core 发起查询时,连接可能直到查询执行前才真正建立。因此堆栈既包含 EF 的 ExecuteNonQueryAsync,也包含 Npgsql 的 Open/NegotiateEncryption,是合理的调用链,不意味着 SQL 在服务端执行到一半才发生 TLS 握手。

10.0.2 的正式实现会让取消异常原样通过

Npgsql 10.0.1 的握手 catch 会包装一般 Exception;10.0.2 的正式源码为此增加 e is not OperationCanceledException 条件。取消由此不再进入“SSL handshake”包装分支。

使用 10.x 的项目可在框架与提供程序依赖允许的范围内采用 10.0.2 或包含同项修正的兼容版本。其他维护分支需要分别核对,不能凭这一个版本号推断所有旧分支的修复情况。EF Core 提供程序与底层 Npgsql 有配套要求,应检查最终解析结果,不仅修改直接引用。

普通异步连接代码仍应把 token 传到真正的 Open 调用。下面是已有异步方法中的片段,连接字符串和 cancellationToken 来自应用:

await using var connection = new Npgsql.NpgsqlConnection(connectionString);
try
{
    await connection.OpenAsync(cancellationToken);
}
catch (OperationCanceledException)
    when (cancellationToken.IsCancellationRequested)
{
    throw;
}

这里没有把取消改成成功,也没有关闭证书验证。若调用方要显示“已取消”,应在自己负责请求结果的边界处理;数据库访问层继续传播取消,便于上层清理工作。升级前的旧包装异常不会被这个 catch 自动识别,应保留异常诊断并修正依赖,不能以示例分支没有进入就判定 token 没有传入。

如果应用暂时需要兼容旧版,限制判断范围到已确认的握手包装、内部取消异常及该操作自己的取消状态,并保留原异常作为原因;不要建立“任何 NpgsqlException 加一个已取消 token 都算取消”的全局规则。token 可能在其他真实错误发生后才被取消。

验证时分别覆盖正常连接、调用方主动取消、连接超时和真实证书错误。主动取消应进入取消路径,后三者不能互相混淆;失败后连接对象与相关资源仍应释放。日志也应记录原始异常类型,而不是只保留“握手失败”四个字。若取消发生在执行命令阶段,应另看命令取消与服务器响应,不把连接阶段的修复推广到整个数据库协议。