fix(harness): 起栈准入数的是「申请过名额的进程」,不是机器上真实存在的栈(2 个租约 vs 18 个栈)
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
## 现象:机器上的栈数和「谁申请过名额」对不上
实测(2026-08-21 10:45 本机,#1704 定位过程中):
```
worktree 总数 111
全机租约文件总数 2 ← 111 个 .harness/state/.cache/stacks/ 加起来
docker compose ls 18 个栈 / 33 个容器
load average 48.33 / 10 核 = 4.83 倍超额
```
**2 个活租约,18 个在跑的栈。**
其中一半(#1704 已修)是 `leaseDir(repoRoot)` 按 worktree 分家造成的——那条已由 PR #1714 收口,租约改成机器全局。
但**剩下的对不上仍然存在**:即使按机器全局算,也只有 2 个进程申请过名额,机器上却有 18 个栈。两种可能,都需要查:
1. **有栈不经 `with-test-isolation` 起**(绕过了准入门);
2. **起完没释放**——`slot.release()` 没跑到(进程被 kill / 崩溃),或者栈本身 `down` 失败留下了。
`activeStacks()` 会清理「进程已死」的租约,所以第 2 种在租约侧会自愈,但**docker 侧的栈不会跟着消失**——这恰好就是「租约数 ≪ 栈数」的形状。
⇒ 准入门数的是**申请过名额的进程**,不是**机器上真实存在的栈**。#1704 修掉了「数错了池子」,没修「数的东西本身不是栈」。
## 相关但独立:有进程在给别人的 postgres 容器发 SIGINT
```
$ docker events --since 90m --format '... {{.Action}} {{name}} sig={{signal}} exit={{exitCode}}'
1787282223 kill wsx-354eeb1c003436872875-postgres-1 sig=2 exit=
1787282231 die wsx-354eeb1c003436872875-postgres-1 sig= exit=0
1787282231 stop wsx-354eeb1c003436872875-postgres-1
```
`sig=2` = **SIGINT**,`exit=0` 干净退出。不是 OOM,也不是 `compose down` 的 SIGTERM(15)。
已排除的:
- **`sweep-docker --apply`**:判据是「worktree 已不在磁盘上 **且** 该项目没有任何运行中容器」,方向明确写着「宁可漏清不可误删」,不会碰活栈(`lib/docker-residue.ts`)。
- **本机内存耗尽**:`OOMKilled=false`,且退出是干净的 0。
SIGINT 的典型来源是**前台 `docker compose up` 被 Ctrl-C / 被上层杀掉时,docker CLI 把 SIGINT 转发给容器**。所以怀疑方向是「某个会话被中断时,连带把它前台起的栈打断了」——但那一例(`wsx-354eeb1c…`)不是我的栈,没法继续追。
⚠ 另外,`#1704` 里我一度把这条 SIGINT 当成我自己三次失败的成因——**那个归因是错的**,我那几次的真成因是 postgres 就绪探针的竞态(PR #1714 第二个 commit)。这里记录 SIGINT 只是因为它确实发生了、且没有解释,**不要**把它当成任何已知红的解释。
## 建议
1. 先补**可观测**:`with-test-isolation` 起栈/清栈时把 compose 项目名 + PID 记进机器全局的一份台账,让「谁起的这个栈」可回答。现在这个问题查不下去,正是因为栈上没有主人信息(只有 compose label 里的 config_files 路径)。
2. 再决定准入门要不要把判据从「租约」换成/补成「真实栈数」。⚠ 注意 `stack-admission.ts` 顶部那条约束仍然成立且必须保留:**不能在准入路径里 shell out 去问 docker**(`docker ps` 在满载时自己会挂,测负载的动作把自己挂住比不测更糟;2026-08-05 事故现场 `docker ps` 超时 >2min)。所以如果要用真实栈数,得由一个**旁路**进程周期性写进台账,准入路径仍然只读文件。
3. `harness sweep-docker` 增加一条「有栈但无主」的巡检项(不自动清——按它自己的「宁可漏清不可误删」)。
## 反证要求
任何门控/巡检加完都要能造出「不修则红」:例如人为起一个不经隔离壳的栈,巡检必须报出它无主;再把它按正常路径起一遍,必须不报。只验一半会做出一个把所有栈都判无主的巡检。
## 来源
#1704 的定位过程与 PR #1714。#1704 修的是「准入门数错了池子」,本 issue 是它剩下的那半。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing with-test-isolation and stack-admission.ts, then review lib/docker-residue.ts and the harness sweep-docker entry point. Define how the machine-global ledger records the Compose project name and PID without shelling out from the admission path. Done means the regression scenarios distinguish an unmanaged stack from one started through the normal isolation path, and the no-owner case is reported without automatic deletion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, docker-compose, typescript
- Domain
- devops, testing, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100