Skip to content

fix(db): 查询会话断连后就地重连,不再需要关闭连接重开 - #447

Merged
feigeCode merged 1 commit into
devfrom
fix/session-reconnect-on-lost-connection
Oct 22, 2026
Merged

feigeCode merged 1 commit into
devfrom
fix/session-reconnect-on-lost-connection

Conversation

@feigeCode

Copy link
Copy Markdown
Owner

问题

「事务: 自动」的查询页签会把第一次执行时建的会话钉住(AutoExecutionSession 按 scope 记住 session_id),之后每条语句都复用同一条物理连接。该会话 in_flight 恒大于 0,连接池的空闲回收与复用前 ping 都碰不到它 —— 空闲期被服务端或防火墙掐断后,页签手里就是一条死连接。

而 PostgreSQL/MySQL 驱动把断连包成 Ok(SqlResult::Error { "connection closed" }) 而不是 Err,UI 的 connection_lost 信号只在 Err 时置位,于是编辑器一直以为会话还活着,每次执行都在几毫秒内失败。唯一的「断开」入口是连接树右键的「关闭连接」,它还会连带关掉该连接的全部页签,所以只能关掉再重开。

改动

  • ConnectionManager::reconnect_session:只替换会话的物理连接,会话 id、池内位置、close_on_release 保持不变,页签与会话状态都不必重建;死连接直接丢弃,不再等 5s 优雅 disconnect 超时;会话记住的 schema 会在新连接上重放,不丢 issue [Bug]: pg查询不带schema #433 的 search_path 语义。
  • GlobalDbState::execute_session:结果全是「语句发送前就已断连」且调用方允许时,就地重连并重试一次;重连失败则原样返回第一次的真实错误。
  • ExecOptions::reconnect_on_lost_connection(默认 false):只有粘性查询会话打开。
  • 结果层兜底:Ok 结果里出现连接级错误也置位 connection_lost,下次执行前重建会话。

安全边界(有意为之)

  • 中途断连不重试:broken pipe / unexpected EOF / connection reset 无法判断语句是否已执行,重放会造成重复写入,只让会话下次重建。
  • 手动事务不重试:新连接没有事务上下文,静默重跑会把语句挪到事务外,保留原有事务语义。

验证

  • cargo test -p db --lib:1370 passed / 0 failed(含新增 7 个测试:就地替换物理连接、schema 重放、statements_never_sent 判定、断连文案分类);本机真库集成测试 real_mysql / real_postgres / real_sqlite 全绿。
  • cargo test -p db_view --lib:956 passed / 0 failed / 1 ignored。
  • cargo clippy -p db -p db_view --all-targets:无 error(--deny warnings 会被 crates/agent_runtime 两个既有 lint 拦下,与本次改动无关)。

「事务: 自动」的查询页签会钉住第一次执行时建的会话,之后每条语句都复用同一条
物理连接。该会话 in_flight 恒大于 0,连接池的空闲回收与复用前 ping 都碰不到它,
空闲期被服务端或防火墙掐断后,页签手里就是一条死连接。而 PG/MySQL 驱动把断连包
成 Ok(SqlResult::Error { "connection closed" }),connection_lost 信号只在 Err 时
置位,于是编辑器一直复用死连接,每次执行都在几毫秒内失败。

- ConnectionManager::reconnect_session:只替换会话的物理连接,会话 id、池内位置、
  close_on_release 保持不变,页签与会话状态都不必重建;死连接直接丢弃,不再等
  优雅 disconnect 超时;会话记住的 schema 会在新连接上重放,不丢 issue #433 的
  search_path 语义。
- GlobalDbState::execute_session:结果全是「语句发送前就已断连」且调用方允许时,
  就地重连并重试一次;重连失败则原样返回第一次的真实错误。
- ExecOptions::reconnect_on_lost_connection(默认 false):只有粘性查询会话打开。
- 中途断连不重试:broken pipe / unexpected EOF / reset 无法判断语句是否已执行,
  重放会造成重复写入。
- 手动事务不重试:新连接没有事务上下文,静默重跑会把语句挪到事务外。
- 结果层兜底:Ok 结果里出现连接级错误也置位 connection_lost,下次执行前重建会话。
@feigeCode
feigeCode merged commit eb437e1 into dev Oct 22, 2026
8 checks passed
feigeCode added a commit that referenced this pull request Oct 22, 2026
#447 给 ExecOptions 新增该字段后漏改 builtin-duckdb 特性下的用例,
--features builtin-duckdb 编译失败(E0063)。
@feigeCode feigeCode mentioned this pull request Oct 22, 2026
feigeCode added a commit that referenced this pull request Oct 22, 2026
参考 dbx 的 checkout 健康检查(get_conn_with_health_check):从池里取连接先
有界 ping,死连接丢弃换新,不依赖错误文案归类。issue #433 / PR #447 引入的
「就地重连重放」把裸 `connection closed` 当作「语句从未发出」来重放整个脚本,
但驱动源码证实这句文案同时覆盖两种情形:

- tokio-postgres:请求通道关闭(未发出)与等待响应时连接任务退出(语句可能
  已被服务端执行)都格式化为 `connection closed`
- mysql_async:读端 EOF(语句已写入 socket)也是同一句

重放可能重复执行已生效的写入。

- execute_session 执行前两段式探活(or 关系):
  1 connection_closed_locally 本地判死(零网络往返):PG 用
     Client::is_closed(),请求通道关闭 = 连接任务已退出,权威信号;
  2) 驱动不支持本地判死时退回有界 ping(network_deadline 10s 上界),
     MySQL 覆写 ping 走真驱动级 COM_PING(不占查询通道、不进 general log)。
  判死即就地重连(会话 id/池内位置/schema 重放不变),语句在重连后的新连接上
  只发出一次,不存在重放窗口。
- 删除执行后的文案重放路径(statements_never_sent 及 retry 分支):错误文案
  不再参与任何重试决策;PRE_SEND_CLOSED_NEEDLES 只保留 tiberius 的确定文案,
  仅用于 looks_like_connection_lost 的 UI「下次重建会话」提示信号。
- cleanup 不再强拆 in_use 会话(idle 与 lifetime 两个维度):粘性会话/手动事务
  被页签钉住时,挂机 30 分钟后首次执行不再报 Session not found,手动事务不再
  被静默回滚;页签关闭路径已有 on_release 释放,不强拆不泄漏。
- 重连探测钳制 connect_timeout 至 10s(只缩短不放大):服务端未恢复时不再让
  用户每次执行都等默认 30s 超时。

验证:db 1363 / db_view 959 通过,clippy 0 警告,check -p main 通过;
探活禁用 / 删 ping 段 / 活连接误判死 三个变异均转红还原。
EOF
)
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