Skip to content

fix(service-datasource): 补 vitest 超时配置覆盖空洞并复核 pool 用例并发隔离 (#6044) - #6101

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-6044-datasource-pool-flake
Aug 7, 2026
Merged

fix(service-datasource): 补 vitest 超时配置覆盖空洞并复核 pool 用例并发隔离 (#6044)#6101
hotlong merged 2 commits into
mainfrom
claude/issue-6044-datasource-pool-flake

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #6044

根因:#4856 的覆盖空洞,不是它已修签名的再现

packages/services/service-datasource 没有任何 vitest 配置文件(包目录仅 package.json / tsconfig.json / tsup.config.ts,test 脚本是裸 vitest run;仓库根也没有 vitest.config.* / vitest.workspace.*,turbo.json 与 workflows 不传任何超时参数——三层核查同 #4856 的方法),因此全部用例吃 vitest 默认 5000ms#4856 修这类 flake 的方式是把 testTimeout 逐包落在各包自己的 vitest.config.ts(spec 60s;driver-mongodb / qa-http-conformance 30s;metadata-fs / plugin-auth 10s),这个结构天然覆盖不到一个没有该文件的包——本包即是空洞。

机制已实测钉死:create() 的 pool 检查(assertDatasourcePoolSupported,default-datasource-driver-factory.ts:345)发生在一切动态 import 之前,所以文件里前两个 reject 用例不加载任何重模块;sqlite WITHOUT a pool still builds(:122)是全文件第一个走到 await import('@objectstack/driver-sql')(knex 及其依赖图)的用例,一次性模块求值成本全落在它身上:

用例 空载实测
sqlite WITHOUT a pool still builds(:122,flaky 签名) 1088ms
postgres still receives the declared pool(骑模块缓存) 31ms
mysql still receives the declared pool(骑模块缓存) 82ms
文件内其余 24 个用例 0-2ms

空载 1088ms 对 5000ms 只有约 4.6 倍余量(#4856 当时的 spec 案例是 1.46 倍),PR 分支单包跑绰绰有余;合并队列全量构建多包并发、I/O/CPU 争用时被吃掉即超时——与签名完全吻合(仅队列全量构建间歇红,受害 PR #5999 两次、#5973 一次,自身 CI 全绿且改动包不含该测试)。

修法:补本包 vitest.config.ts,结构照姊妹包(plugin-auth 形状:defineConfig + environment: 'node',本包测试显式 import { … } from 'vitest' 故不需要 globals,不发明多余字段),testTimeout: 60_000 沿用 #4856 的取值不引入新数字,落在配置层使后续新增用例到达即被覆盖。60s 对实测 1.1s 约 55 倍余量,吸收队列争用而不掩盖真挂起。

并发隔离性复核结论(⛔ 分诊座位明令不许只调超时收尾——已逐项复核,未发现真实泄漏)

通读 datasource-pool-support.test.ts 全部 27 个用例 + factory 构建路径(default-datasource-driver-factory.tssqlite-driver-fallback.ts → driver-sql SqlDriver),逐项:

  • 文件:sqlite 构建落的是 :memory:(测试显式传入;buildSqlConnection 的 fallback 也是 :memory:),且 dev: false 走生产路径 buildNative() 不探测(sqlite-driver-fallback.ts:130-132)——不 connect、不跑查询,连内存库对象都不会创建,better-sqlite3 的 native addon 惰性到首次 acquire 才加载(本用例从未触发)。零文件 I/O,无临时路径 ⇒ 无跨用例路径碰撞面。(包内确用临时文件的是姊妹文件 default-datasource-driver-factory.test.ts,其用 mkdtempSync 唯一路径 + afterAll 清理,且 flaky 文件与它无共享路径。)
  • 句柄:SqlDriver 构造期建 knex 实例(tarn pool 空,不开连接、不起定时器),Door-1 三个构建成功的用例均 await handle.disconnect?.()knex.destroy() 销毁空 pool;postgres(db.internal)/ mysql(localhost:3306)用例 knex 惰性,从不拨号。两个 reject 用例在构造之前就 throw,无任何分配。无 socket、无 pool 句柄、无 native 句柄存活跨用例。(残余小点:disconnect 在断言之后无 try/finally,断言失败时 knex 实例不销毁——但空 tarn pool 无 OS 句柄/定时器,worker 随文件退出,构不成泄漏。)
  • 跨用例共享可变状态:factory() 每用例新建(工厂无模块态);模块级 sqliteWithPool 常量从不被就地修改(唯一变体走 spread 拷贝);Door-2/3 的 svc() / adminHarness() 每用例新建 engine/factory/records;dev: false 显式传入,不依赖 process.env,文件不改任何 env。唯一跨用例共享是 Node 模块缓存——by design,且正是「只有第一个重 import 用例慢」的原因
  • 结论:该红是真实一次性导入成本上的负载方差,不是隔离缺陷;无需改测试逻辑,修法即补配置层覆盖。

反向验证(方向预先判定)

6 秒睡眠探针用例(临时,未提交):改动前按预测以 Error: Test timed out in 5000ms. 变红(证明默认层今天就是生效层)→ 加配置后按预测 6.20s 变绿(证明包级配置即生效层)。探针已删除,提交 diff 仅 vitest.config.ts + changeset 两个文件。

验证清单

  • pnpm --filter @objectstack/service-datasource test(vitest run --reporter=verbose --maxWorkers=2):改前基线与改后各一轮,均 Test Files 12 passed (12) / Tests 230 passed (230);:122 用例耗时见上表,已佐证「5s 默认值对真实构建过紧」。
  • pnpm --filter @objectstack/service-datasource typecheck:通过(tsc --noEmit 无输出)。
  • pnpm --filter @objectstack/service-datasource build(tsup):成功(CJS/ESM/DTS)。
  • root 门(lint.yml 相关项):pnpm lint 通过;pnpm check:nul-bytes OK;pnpm check:published-files 通过(本包 files 白名单为 dist/README/CHANGELOG,vitest.config.ts 不进发布面,门自测亦确认白名单不收 test-harness config);pnpm check:type-check-coverage OK。
  • changeset:按 fix(spec): 给 packages/spec 的 vitest 设 testTimeout 60s —— 止血,不再把无关 PR 踢出合并队列 (#4850) #4856 先例(.changeset/spec-vitest-testtimeout.md,patch + 「test configuration only」)给 @objectstack/service-datasource 加 patch changeset,Check Changeset 门以此为绿。
  • 未触 packages/spec,未动 content/docs/releases/

台账

落地后请队列管家清 #5810 台账对应行(签名:datasource-pool-support sqlite WITHOUT a pool)。

Refs #4856(模式来源)/ #4850(同类根因)/ #5714 / PR #5954(用例来源)/ #5810(管家台账锚点)。


Generated by Claude Code

两条腿:

1. 覆盖空洞:本包此前没有任何 vitest 配置文件,全部用例吃 vitest 默认
   5000ms —— #4856 把 testTimeout 逐包落在各包自己的 vitest.config.ts,
   结构上覆盖不到没有该文件的本包。补 vitest.config.ts,testTimeout: 60_000
   沿用 #4856 的取值不引入新数字,落在配置层使后续新增用例到达即被覆盖。
   实测:flaky 用例(datasource-pool-support :122)是全文件第一个走到
   `await import('@objectstack/driver-sql')`(knex)的用例,空载 1088ms,
   对 5000ms 只有约 4.6 倍余量,队列全量并发下被吃掉即超时 —— 与签名吻合
   (仅队列全量构建间歇红,受害 PR 自身全绿)。探针复核:6s 睡眠用例改前
   以 "Test timed out in 5000ms" 变红、改后 6.2s 变绿,证明包级配置即生效层。

2. 并发隔离复核(分诊座位明令不许只调超时收尾):逐项排查文件/句柄/共享
   状态 —— sqlite 构建落 :memory: 且生产路径不探测、不开连接、native addon
   惰性不加载,零文件 I/O;Door-1 各用例 disconnect 销毁空 knex pool;
   Door-2/3 全部用 per-case fake;无临时文件、无端口、无跨用例可变共享状态。
   结论:该红是真实一次性导入成本上的负载方差,不是泄漏,无需修隔离。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015a5qkLzpGXhLL2F5gvJ7dD
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 1:58am

Request Review

@github-actions github-actions Bot added the size/s label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 1 changed package(s). ✅

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026

hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

ESLint job 红 = base 侧共享损伤,非本 PR(services 座位 PM,一次性说明)


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants