Repository navigation
Commit cba7d8a
test(example-todo): boot the loop-body kernel once per file under a measured budget (#31312)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiWpz18m9kVbxeipdp31C27T)_
Fixes #31298
This is also the fix for the queue-flake anchor #31294 (same file, same
case); the anchor can be closed with this PR.
Clause-②: no — test-only; no accept set, schema or export changes
Metadata-protocol: none
## 维护者速读
- 改了什么:`examples/app-todo/test/loop-body-iteration.test.ts`
不再每条用例各启动一个完整的 todo kernel,改为整个文件在 `beforeAll` 里启动一次(带 30 秒实测预算),每条用例前清空
`todo_task` 行和捕获的通知。五条用例的断言一字未改。只改这一个测试文件。
- 为什么改:启动约占每条用例耗时的 80%。在负载高的 Test Core 分片上,第一条用例(启动 + 一次执行)三次被 vitest 默认
5 秒截断(5011 / 5015 / 5033 ms),把与它无关的 PR 踢出合并队列(#31249、#31258),#31279
也因此红了两次。
- #31298 的假设已实测排除:去掉 #31230 加的三个源码别名,本文件耗时不变(这个文件的导入链根本不经过那三个包),所以
`vitest.config.ts` 不改。
- 风险与回滚:只改测试文件,不发布(skip-changeset)。隔离已验证:4 种乱序和 5 条单跑都绿;去掉每例前的清空则 5 条里 4
条红。回滚即 revert。
- 两分钟验证:`pnpm --filter @objectstack/example-todo exec vitest run
test/loop-body-iteration.test.ts`,预期 5/5 通过。
- 需要你做的:无,席位落地。
## What changes
- `examples/app-todo/test/loop-body-iteration.test.ts` only.
- `bootTodoKernel()` runs once per file in `beforeAll`, under an
explicit 30 000 ms budget (`BOOT_BUDGET_MS`) with the measurement
written beside it. It used to run inside each of the five cases, under
vitest's 5 s per-test default.
- `beforeEach` gives each case the state a fresh boot leaves: every
`todo_task` row is deleted through the driver (so no hook or flow
observes the reset), and the captured `messaging.emit()` list is
emptied.
- CONTROL removes the fixture flow it registers (`unregisterFlow`) in a
`finally`, so no case leaves a registration behind.
- `afterAll` disconnects the one driver. This replaces the per-case
`afterEach` driver list.
- Every assertion is unchanged. The doc block says why the suite shares
one kernel and how each case still gets a clean state.
## Why
The three failing Test Core jobs all cut off the same case, the file's
first:
| job | ESCALATION | EVERY | other 3 | package Duration |
|---|---|---|---|---|
| 128565270519 (#31279) | 5011 ms, timed out | 1356 ms | 344–502 ms |
50.13 s |
| 128519296820 (queue 42410962891, #31249) | 5033 ms, timed out | 4620
ms | 563–746 ms | 78.25 s |
| 128555004931 (queue 42422774934, #31258) | 5015 ms, timed out | 1691
ms | 416–508 ms | 53.55 s |
Each log shows the case past kernel bootstrap and object registration
when the timeout hit. `afterEach` then closed the driver under it and
printed `Acquire connection error: Error: aborted` three times. That is
slowness, not an assertion failure.
Locally, idle, a case took 90–330 ms, and boot was about 80% of it. Boot
phases measured inside the first case: `kernel.use` 3 ms, `bootstrap`
31–42 ms, sqlite-wasm `connect` 35 ms, `syncSchemas` 52–82 ms, Default
Organization 45–87 ms, registering the flows 32–44 ms. The `execute`
itself took 22–25 ms. So each case put a whole kernel boot under the 5 s
per-test line, and on a shard running about 20x slower than an idle box
(one warm case took 4620 ms), the first one crossed it.
### #31298's hypothesis, measured: the #31230 aliases are not the cost
#31298 asked for cold and warm timings with and without the package-wide
source aliases in `examples/app-todo/vitest.config.ts`
(`@objectstack/runtime`, `@objectstack/plugin-auth`,
`@objectstack/mcp`). Measured on this container (4 cores), whole example
suite, test file from `main` `7c523aa1`. "Cold" means
`node_modules/.vite` removed first. "Load" means 8 CPU-bound processes
running beside the suite. The numbers are the five case times of this
file, in ms:
| variant | cold | warm | load (two runs) | suite wall idle / load |
|---|---|---|---|---|
| A: `main` (aliases on) | 296,112,224,190,189 | 306,89,219,169,162 /
300,123,242,171,162 | 880,332,379,375,272 / 608,234,345,272,238 | 15.4 s
/ 37.2–37.5 s |
| B: aliases removed | 507,106,257,206,183 | 338,124,234,178,171 /
268,106,225,173,157 | 831,243,429,359,312 / 691,343,466,340,277 |
15.0–15.4 s / 34.7–37.9 s |
| C: aliases on, `mcp-actions.test.ts` excluded | 266,77,203,166,165 |
283,118,216,186,173 | 852,285,384,332,308 / 813,376,425,379,264 | 9.7 s
/ 22.7–22.9 s |
A and B are the same within noise. That fits the import graph: none of
the `dist/` of `core`, `objectql`, `driver-sql`, `driver-sqlite-wasm` or
`service-automation`, which are all this file imports from the platform,
imports any of the three aliased packages (0 files). Narrowing the
aliases would not change this file. So `vitest.config.ts` is not
touched.
What #31230 did change is the package's load. Its `mcp-actions.test.ts`
joined the suite and adds about 6 s of idle wall and about 15 s under
load (A against C), running in parallel with this file on the same
shard. That makes the shard busier. It does not explain the cliff by
itself: this file's own case times are the same in A and C.
## Tests
After the fix, on this container, JSON reporter, case times in ms:
- Idle, whole suite: 48, 34, 29, 11, 18. Under 8 CPU hogs: 132, 97, 56,
29, 36. Before the fix the same runs gave 296–880 for the first case.
- File alone, idle: `tests 273–275ms` for the file, boot included. It
was `tests 850–874ms` before.
- Isolation: `--sequence.shuffle` with seeds 1, 7, 42 and 1234: 5/5
passed in each order (for example CONTROL, CONTAINMENT, ESCALATION,
EVERY, REMINDER). Each case alone (`-t ESCALATION`, `-t EVERY`, `-t
CONTAINMENT`, `-t REMINDER`, `-t CONTROL`): 1 passed, 4 skipped, each
time.
- Reverse check: with the `beforeEach` reset removed, 4 of 5 cases fail.
So the reset is what keeps the shared kernel's cases independent, and it
works.
- Sibling files that also boot a kernel: in the same three failing jobs
their slowest single case was 2060 ms (task-recurrence `daily`), then
2019 ms and 1888 ms, and task-completion GATES 1763 ms. Locally idle
they take 700–1118 ms. None is at the 5 s line, so they are not changed
here (see Acceptance notes).
- The whole example suite, 5 runs in a row on `877170f8`: `pnpm --filter
@objectstack/example-todo exec vitest run`, each exit 0 with `Test Files
9 passed (9)`, `Tests 249 passed (249)`.
## Acceptance notes
- The boot budget is per-file (`beforeAll`, 30 s), not per case. Each
case now runs no boot and stays under 150 ms locally even under load,
far inside the 5 s default, so the cases keep vitest's default.
- Sibling files in `examples/app-todo/test` that boot a kernel per case:
`task-recurrence.test.ts` (4 end-to-end cases, slowest 2060 ms in CI),
`task-completion-trigger.test.ts` (slowest 1763 ms),
`derived-flag-removal.test.ts` (slowest 1079 ms). Not at the cliff in
any of the three failing jobs, so not touched. If one of them is ever
cut off, the same one-kernel-per-file shape applies.
- `mcp-actions.test.ts` (from #31230) adds about 6 s of idle suite wall
to this package (15.4 s against 9.7 s without it). Its cost is the same
with and without the aliases. Not a defect; noted as the package's load.
## Verification
All on `877170f8` (this branch with `origin/main` merged in, no rebase):
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 37 commands. Every one ran with exit
0. Before the run, `pnpm exec turbo run build --filter='./packages/**'
--concurrency=2` built every package, because
`check:dual-build-cjs-loads` reads built output and exited 3
(prerequisite not met) without it. `--ran`: "37 derived famil(ies)
accounted for — 37 run, 0 NOT-MEASURED".
- `pnpm lint`: exit 0.
- `pnpm --filter @objectstack/example-todo typecheck`: exit 0. The
program includes `test/loop-body-iteration.test.ts`, counted with
`--listFiles`.
- Not run locally: `test:repo` (the vitest `repo` project), because this
diff does not touch it. Test Core runs it on this PR.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(Not touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (Test-only in
a private example package; nothing publishes: `skip-changeset`.)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2etWp2cPA9ARe5c6hN)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>1 parent f8c3ecc commit cba7d8a
1 file changed
Lines changed: 53 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
37 | 48 | | |
38 | 49 | | |
39 | | - | |
| 50 | + | |
40 | 51 | | |
41 | 52 | | |
42 | 53 | | |
| |||
55 | 66 | | |
56 | 67 | | |
57 | 68 | | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | 69 | | |
66 | 70 | | |
67 | 71 | | |
| |||
74 | 78 | | |
75 | 79 | | |
76 | 80 | | |
77 | | - | |
| 81 | + | |
78 | 82 | | |
79 | 83 | | |
| 84 | + | |
80 | 85 | | |
81 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
82 | 89 | | |
83 | 90 | | |
84 | 91 | | |
| |||
107 | 114 | | |
108 | 115 | | |
109 | 116 | | |
110 | | - | |
111 | 117 | | |
112 | 118 | | |
113 | 119 | | |
| |||
116 | 122 | | |
117 | 123 | | |
118 | 124 | | |
119 | | - | |
| 125 | + | |
120 | 126 | | |
121 | 127 | | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
122 | 152 | | |
123 | 153 | | |
124 | 154 | | |
| |||
132 | 162 | | |
133 | 163 | | |
134 | 164 | | |
135 | | - | |
| 165 | + | |
136 | 166 | | |
137 | 167 | | |
138 | 168 | | |
| |||
158 | 188 | | |
159 | 189 | | |
160 | 190 | | |
161 | | - | |
| 191 | + | |
162 | 192 | | |
163 | 193 | | |
164 | 194 | | |
| |||
184 | 214 | | |
185 | 215 | | |
186 | 216 | | |
187 | | - | |
| 217 | + | |
188 | 218 | | |
189 | 219 | | |
190 | 220 | | |
| |||
200 | 230 | | |
201 | 231 | | |
202 | 232 | | |
203 | | - | |
| 233 | + | |
204 | 234 | | |
205 | 235 | | |
206 | 236 | | |
| |||
216 | 246 | | |
217 | 247 | | |
218 | 248 | | |
219 | | - | |
| 249 | + | |
220 | 250 | | |
221 | 251 | | |
222 | 252 | | |
| |||
237 | 267 | | |
238 | 268 | | |
239 | 269 | | |
240 | | - | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
241 | 276 | | |
242 | 277 | | |
243 | 278 | | |
| |||
0 commit comments