Repository navigation
[Bug]: COM_QUIT removes session but leaves client TCP socket open #30978
Description
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
- Reproducer: 10/10(2 秒等待)及独立客户端配置矩阵 18/18(1.5 秒等待)。
- Control: 每条连接在
COM_QUIT前SELECT 1返回数值1;224 握手和认证成功,说明不是 [Bug]: mysql2 default collation 224 handshake is rejected although SHOW COLLATION reports utf8mb4_unicode_ci compiled #30014 的原始握手失败。 - Atomicity / cleanup: 无数据写入;每次错误观察后主动销毁测试 socket。隔离服务在全部测试后仍能接受查询;逻辑旧会话 10/10 不在进程列表中。
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
- [Bug]: mysql2 default collation 224 handshake is rejected although SHOW COLLATION reports utf8mb4_unicode_ci compiled #30014:原始 collation 224 握手问题已由 fix(frontend): admit native Unicode handshake collations #30093 修复,本 issue 仅追踪独立的
COM_QUIT物理连接残留。 - 已搜索本仓库 open/closed 的
COM_QUIT、mysql2 end、连接未关闭相关 issue;未发现同一 CN 前端 socket 关闭缺陷的准确重复项。历史 bug(proxy): TestQuitEvent can hang after terminal QUIT closes request channel #26141 是 Proxy QUIT 事件通道挂起,路径不同。
Activity