Skip to content

[Bug]: COM_QUIT removes session but leaves client TCP socket open #30978

Open

Description

@Ariznawlll

Description

mysql2 客户端发送正常 COM_QUIT(await connection.end())后,MatrixOne 移除了逻辑会话,但没有关闭对应物理 TCP socket。客户端的 end() Promise 已返回,继续等待仍无 close 事件,连接保持 readyState=open。这与 #30014 的 collation 224 握手拒绝不同:握手、查询和参数预编译均已通过,异常只在关闭阶段。

Environment

  • Branch: 官方 main
  • Commit: f8c872001603fed3d23784e1ada26f9a2d8f4fba
  • Deployment: 该完整 SHA 的干净隔离工作树编译的 mo-service,Darwin/arm64,本地 1 LogService / 1 TN / 1 CN,真实 MySQL TCP;运行实例 git_version()=f8c8720016。
  • Client: Node.js mysql2 3.24.5,charsetNumber: 224;2026-10-23。

Steps to reproduce

在只供测试的 MO 实例上,设置连接地址与测试用户为环境变量,运行:

const mysql = require('mysql2/promise');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const c = await mysql.createConnection({
  host: process.env.MO_HOST,
  port: Number(process.env.MO_PORT),
  user: process.env.MO_USER,
  password: process.env.MO_PASSWORD,
  charsetNumber: 224,
});
const socket = c.connection.stream;
let closeEvent = false;
socket.once('close', () => { closeEvent = true; });
const [rows] = await c.query('SELECT 1 AS v');
if (rows[0].v !== 1) throw new Error('query control failed');
await c.end();
await sleep(2000);
console.log({closeEvent, destroyed: socket.destroyed,
             readyState: socket.readyState});
socket.destroy(); // 测试清理,避免遗留物理连接

Actual behavior

上述真实 mysql2 路径在 10/10 个独立连接中均输出 closeEvent=false, destroyed=false, readyState=open。每次关闭后,另一条正常连接执行 SHOW PROCESSLIST 已找不到旧 connection id(10/10);即会话对象退出不等于 TCP 被关闭。测试随后显式 socket.destroy() 清理。额外的 6 种连接配置 × 3 轮矩阵(默认、224、45、显式 Unicode、无 CONNECT_ATTRS、无 PLUGIN_AUTH_LENENC_CLIENT_DATA)中,查询和预编译均成功,但 end() 后等待 1.5 秒的物理连接仍 18/18 保持 open。

Expected behavior

处理 COM_QUIT 后服务端应终止连接并使客户端收到物理 socket 的 close;不能仅删除逻辑会话而无限等待该客户端继续发送请求。

Stability and controls

Evidence

  • 源 issue 的先前 QA 证据:matrixone#30014 验收评论。本次使用更新的完整 main SHA 再复核,结果相同。
  • 复现脚本及输出保存在本次隔离 QA 临时目录 /private/tmp/mo-remaining-qa-HzYC1B/qa_com_quit.js;核心结果:physicallyOpen=10, logicalSessionGone=10。

Code analysis

当前 main pkg/frontend/mysql_cmd_executor.go 的 COM_QUIT 分支返回 moerr.GetMysqlClientQuit();pkg/frontend/server.go 的 handleRequest 对 skipClientQuit(err.Error()) 返回 nil,外层 handleMessage 因此可能继续等待下一包,而不是主动结束连接。这是与观测吻合的排查线索,尚未用服务端协议 trace 证明为唯一根因。

Regression coverage

修复后应在 MOTR 的多客户端/协议场景中断言 mysql2.end() 后客户端物理 socket 在有界时间内关闭,同时保留 collation 224 握手与普通查询对照。当前产品失败不应写成成功 golden;按用户要求,新增回归仅进入 MOTR #278,不向 MO 仓库提交 PR。

Related

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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions