Przeglądaj źródła

Merge pull request #3028 from deepseek-harness/worktree/fix-windows-coverage-worker-exit

fix(ci): order native Windows coverage after build
Tianyi Cui 1 miesiąc temu
rodzic
commit
f8b0ca046f

+ 2 - 2
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
-2026-08-08-native-windows-pull-request-ci.md: 1f8bf7c9e5249ce218fd0d169ed82008c2dbbd36
-2026-08-08-native-windows-pull-request-ci.zh.md: efe044e601aebc92f5d9446a1a683c935dcd783b
+2026-08-08-native-windows-pull-request-ci.md: a1a783df2a700eec5436abbb144849629bbecebc
+2026-08-08-native-windows-pull-request-ci.zh.md: 42db5aa974c1d1dca6c6151d6c058f39571dcc59

Plik diff jest za duży
+ 0 - 0
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md


Plik diff jest za duży
+ 0 - 0
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md


+ 2 - 2
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md
-2026-08-18-in-job-partitioned-coverage.md: 532c145f5b66bd6574f9ee167c12c739fd4d7fa9
-2026-08-18-in-job-partitioned-coverage.zh.md: dc8a089a7c089338b775e49fdfe67f134f077704
+2026-08-18-in-job-partitioned-coverage.md: 2cf06f6dd3eb9cdfd563aaa01ecd4ea9b8fb376f
+2026-08-18-in-job-partitioned-coverage.zh.md: 266feed66a28833488973db66fbd1f3476bf9ba7

+ 4 - 4
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md

@@ -18,7 +18,7 @@ When partitioning is enabled, `scripts/run-gates.ts` selects `pnpm run test:cove
 
 The coordinator waits for every child, validates that the blob directory contains exactly the expected files, and then runs one `vitest --merge-reports ... --coverage` command. Only that merged command applies the repository's per-file statement, branch, function, and line thresholds, so a partition is never judged against an intentionally partial inventory.
 
-`DSH_COVERAGE_MAX_WORKERS` continues to size the uninstrumented exempt gate and the ordinary non-partitioned path; it does not resize partition children. Native Windows gives the exempt gate two workers and admits four concurrent outer gates. Build, production-site validation, and instrumented coverage start immediately; exempt-heavy coverage starts only after build passes, preventing its temporary Oxlint probes from racing source compilation. The observational inventory waits only for both coverage gates to settle, so it still runs after a coverage failure; each gate's `needs` dependencies remain pass-required. Linux overlaps four instrumented partition processes with two exempt workers, restoring the ordinary path's former four-way instrumented concurrency while keeping every instrumented process single-worker.
+`DSH_COVERAGE_MAX_WORKERS` continues to size the uninstrumented exempt gate and the ordinary non-partitioned path; it does not resize partition children. Native Windows gives the exempt gate two workers and admits four concurrent outer gates. The workspace build and production-site validation start immediately; both coverage gates wait for the complete build. The instrumented suite contains packer assertions over built `lib/` output, so this dependency prevents it from reading a partially emitted package closure, while also preventing the exempt gate's temporary Oxlint probes from racing source compilation. The observational inventory waits only for both coverage gates to settle, so it still runs after a coverage failure; each gate's `needs` dependencies remain pass-required. Linux overlaps four instrumented partition processes with two exempt workers, restoring the ordinary path's former four-way instrumented concurrency while keeping every instrumented process single-worker.
 
 ## Failure and output semantics
 
@@ -28,9 +28,9 @@ A normal failed test still emits a blob through `--coverage.reportOnFailure`, al
 
 ## Verification
 
-`scripts/coverage-partitions.spec.ts` pins argument construction, package-script separator removal, one-worker partitions, the single merged threshold command, failed-test merging, failure diagnostics before complete-blob validation, waiting for sibling partitions after a spawn failure, and link-safe cleanup. `scripts/run-gates.spec.ts` pins opt-in selection, invalid-count rejection, the complete Windows inventory with its blocking split, and unbuffered streamed output. React fake-timer cases that can move between partitions advance timers inside `act()`; geometry-dependent portal tests stub their element rectangles so a different shard schedule cannot turn deferred updates or jsdom coordinates into coverage-only failures.
+`scripts/coverage-partitions.spec.ts` pins argument construction, package-script separator removal, one-worker partitions, the single merged threshold command, failed-test merging, failure diagnostics before complete-blob validation, waiting for sibling partitions after a spawn failure, and link-safe cleanup. `scripts/run-gates.spec.ts` pins opt-in selection, invalid-count rejection, both native Windows coverage gates' complete-build dependency, the complete Windows inventory with its blocking split, and unbuffered streamed output. React fake-timer cases that can move between partitions advance timers inside `act()`; geometry-dependent portal tests stub their element rectangles so a different shard schedule cannot turn deferred updates or jsdom coordinates into coverage-only failures.
 
-Completed native Windows comparisons measured two partitions near 405 seconds and sixteen partitions at 112.66–122.01 seconds, but the sixteen-way schedule could put more than twenty active execution units beside build and exempt coverage on a 16-core runner. Eight partitions keep separate-process isolation while accepting a longer feedback path for a materially lower peak. Two Linux samples measured the conservative two-partition configuration at 276.68 and 282.27 seconds; that configuration was stable but halved the ordinary path's four instrumented workers. Four partitions restore that fan-out, for six total coverage execution units on the 16-core hosted runner and at most 36 across the failover VM's six runner instances. These values come from completed runs or fixed capacity bounds; an unfinished run crossing an arbitrary elapsed-time mark is not evidence for increasing concurrency.
+Completed native Windows comparisons measured two partitions near 405 seconds and sixteen partitions at 112.66–122.01 seconds under the earlier gate ordering; those values compare partition latency, not the current peak. The current post-build phase runs eight instrumented partition processes beside two exempt workers, for ten coverage execution units. Sixteen partitions would raise that phase to eighteen before any still-running production-site work or system overhead. Eight keeps separate-process isolation while leaving capacity headroom. Two Linux samples measured the conservative two-partition configuration at 276.68 and 282.27 seconds; that configuration was stable but halved the ordinary path's four instrumented workers. Four partitions restore that fan-out, for six total coverage execution units on the 16-core hosted runner and at most 36 across the failover VM's six runner instances. These values come from completed runs or fixed capacity bounds; an unfinished run crossing an arbitrary elapsed-time mark is not evidence for increasing concurrency.
 
 ## Alternatives considered
 
@@ -46,6 +46,6 @@ Completed native Windows comparisons measured two partitions near 405 seconds an
 
 Coverage pays one Vitest startup/configuration cost per partition and one report-merge cost, but it avoids another workflow topology and keeps one final threshold verdict. Partition output may interleave, while the partition start labels and Vitest file identities retain attribution.
 
-Linux and Windows use the same coordinator with platform-specific partition counts and surrounding worker budgets. Local coverage stays simple unless a caller explicitly chooses the partitioned package script and supplies a valid count greater than one.
+Linux and Windows use the same coordinator with platform-specific partition counts and surrounding worker budgets. Native Windows starts both coverage gates after the complete build because its instrumented corpus can consume built artifacts; Linux's dedicated coverage job does not share a workspace with a concurrent build. Local coverage stays simple unless a caller explicitly chooses the partitioned package script and supplies a valid count greater than one.
 
 Future tuning starts from completed runs at one fixed configuration. Slow progress alone never raises partition count or outer concurrency, because repeated restarts would erase the only evidence needed to choose a stable setting.

+ 4 - 4
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 
 协调器等待全部子进程结束,验证 blob 目录只包含预期文件,然后执行一次 `vitest --merge-reports ... --coverage`。只有这条合并命令应用仓库的逐文件语句、分支、函数与行阈值,因此系统不会拿有意不完整的测试清单单独判定任一分区。
 
-`DSH_COVERAGE_MAX_WORKERS` 继续控制无插桩豁免门禁和普通非分区路径的规模,不会调整分区子进程。原生 Windows 为豁免门禁分配 2 个 worker,并允许 4 道外层门禁并发。构建、生产网站验证与插桩覆盖率会立即启动;豁免重型覆盖率只在构建通过后启动,避免其临时 Oxlint 探针与源码编译竞态。观测性清单只等待两道覆盖率门禁结算,因此在覆盖率失败后仍会运行;各门禁自身的 `needs` 依赖仍要求前置门禁通过。Linux 让 4 个插桩分区进程与 2 个豁免 worker 重叠运行,在保持每个插桩进程只有 1 个 worker 的同时,恢复普通路径原有的 4 路插桩并发。
+`DSH_COVERAGE_MAX_WORKERS` 继续控制无插桩豁免门禁和普通非分区路径的规模,不会调整分区子进程。原生 Windows 为豁免门禁分配 2 个 worker,并允许 4 道外层门禁并发。工作区构建与生产网站验证会立即启动;两道覆盖率门禁都等待完整构建。插桩套件包含针对已构建 `lib/` 输出的打包器断言,因此这项依赖可避免它读取只完成部分产出的包闭包,也可避免豁免门禁的临时 Oxlint 探针与源码编译竞态。观测性清单只等待两道覆盖率门禁结算,因此在覆盖率失败后仍会运行;各门禁自身的 `needs` 依赖仍要求前置门禁通过。Linux 让 4 个插桩分区进程与 2 个豁免 worker 重叠运行,在保持每个插桩进程只有 1 个 worker 的同时,恢复普通路径原有的 4 路插桩并发。
 
 ## 失败与输出语义
 
@@ -28,9 +28,9 @@ Status: implemented
 
 ## 验证
 
-`scripts/coverage-partitions.spec.ts` 固定了参数构造、包脚本分隔符移除、单 worker 分区、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。`scripts/run-gates.spec.ts` 固定了显式启用、非法数量拒绝、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 `act()` 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。
+`scripts/coverage-partitions.spec.ts` 固定了参数构造、包脚本分隔符移除、单 worker 分区、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。`scripts/run-gates.spec.ts` 固定了显式启用、非法数量拒绝、两道原生 Windows 覆盖率门禁对完整构建的依赖、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 `act()` 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。
 
-已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒,但 16 路调度与构建、豁免覆盖率并行时,会在 16 核运行器上形成超过 20 个活动执行单元。8 个分区继续保留独立进程隔离,同时接受更长的反馈路径,以显著降低峰值。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。
+已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒;这些数据来自先前的门禁顺序,只用于比较分区延迟,不代表当前峰值。当前的构建后阶段会让 8 个插桩分区进程与 2 个豁免 worker 并行,共形成 10 个覆盖率执行单元。若改为 16 个分区,则在尚未结束的生产网站工作或系统开销计入之前,该阶段就会达到 18 个执行单元。8 个分区既保留独立进程隔离,也为其他工作留出容量余量。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。
 
 ## 曾考虑的替代方案
 
@@ -46,6 +46,6 @@ Status: implemented
 
 每个分区都要支付 1 次 Vitest 启动与配置开销,最后还要执行 1 次报告合并,但它不引入另一套工作流拓扑,并保留唯一的最终阈值判定。分区输出可能交错,但分区启动标签和 Vitest 文件标识仍可用于归因。
 
-Linux 与 Windows 使用相同的协调器,并各自设置分区数量与外围 worker 预算。本地覆盖率默认保持简单;只有调用方显式选择分区包脚本并提供大于 1 的合法数量时,才启用分区。
+Linux 与 Windows 使用相同的协调器,并各自设置分区数量与外围 worker 预算。原生 Windows 会在完整构建之后启动两道覆盖率门禁,因为其插桩语料可能消费构建产物;Linux 的专用覆盖率 job 不会与同一工作区中的并发构建共享目录。本地覆盖率默认保持简单;只有调用方显式选择分区包脚本并提供大于 1 的合法数量时,才启用分区。
 
 未来调优从一个固定配置的完整运行开始。进度缓慢本身绝不会提高分区数量或外层并发,因为反复重启会抹掉选择稳定设置所需的唯一证据。

+ 19 - 10
packages/session/session-persistence-sqlite/tests/sqlite.spec.ts

@@ -498,19 +498,28 @@ describe('SessionPersistenceSqlite schema ownership', () => {
   })
 
   it('paces repeated busy journal-mode attempts', async () => {
-    let attempts = 0
-    const BusyDatabase = databaseWithJournalFailure(() => {
-      attempts += 1
-      return Object.assign(new Error('database is locked'), { errcode: 5 })
+    const attemptedAt: number[] = []
+    const BusyTwiceDatabase = databaseWithJournalFailure(() => {
+      attemptedAt.push(performance.now())
+      return attemptedAt.length <= 2
+        ? Object.assign(new Error('database is locked'), { errcode: 5 })
+        : undefined
     })
-    await expect(openDatabase(
-      BusyDatabase,
+    const db = await openDatabase(
+      BusyTwiceDatabase,
       await freshDbPath('dsh-sqlite-journal-paced-'),
       'wal',
-      50,
-    )).rejects.toThrow('database is locked')
-    expect(attempts).toBeGreaterThan(1)
-    expect(attempts).toBeLessThanOrEqual(6)
+      DEFAULT_BUSY_TIMEOUT_MS,
+    )
+    db.close()
+
+    expect(attemptedAt).toHaveLength(3)
+    for (let index = 1; index < attemptedAt.length; index += 1) {
+      const previous = attemptedAt[index - 1]
+      const current = attemptedAt[index]
+      if (previous === undefined || current === undefined) throw new Error('missing journal attempt timestamp')
+      expect(current - previous).toBeGreaterThanOrEqual(5)
+    }
   })
 
   it('rejects unversioned, incompatible, and foreign-application databases', async () => {

+ 2 - 1
scripts/run-gates.spec.ts

@@ -160,7 +160,7 @@ describe('gate graph validation', () => {
     },
   )
 
-  it('keeps native Windows coverage blocking while retaining the observational inventory', () => {
+  it('keeps native Windows coverage blocking and behind the complete build', () => {
     const complete = withPnpmEntrypoint(() => gatesForMode('ci-windows-complete'))
     const observational = withPnpmEntrypoint(() => gatesForMode('ci-windows-observational'))
       .filter(gate => gate.id !== 'build' && gate.id !== 'docs-site-build')
@@ -168,6 +168,7 @@ describe('gate graph validation', () => {
 
     expect(byId.get('coverage')?.allowFailure).not.toBe(true)
     expect(byId.get('coverage-exempt-heavy')?.allowFailure).not.toBe(true)
+    expect(byId.get('coverage')?.needs).toContain('build')
     expect(byId.get('coverage-exempt-heavy')?.needs).toContain('build')
     expect(observational).not.toHaveLength(0)
     for (const gate of observational) {

+ 4 - 3
scripts/run-gates.ts

@@ -473,9 +473,10 @@ function ciWindowsBlockingGates(): Gate[] {
 }
 
 function ciWindowsCompleteGates(): Gate[] {
-  const coverage = coverageGates().map(gate => gate.id === 'coverage-exempt-heavy'
-    ? { ...gate, needs: [...new Set(['build', ...(gate.needs ?? [])])] }
-    : gate)
+  const coverage = coverageGates().map(gate => ({
+    ...gate,
+    needs: [...new Set(['build', ...(gate.needs ?? [])])],
+  }))
   const coverageAfter = coverage.map(gate => gate.id)
   const observational = ciWindowsObservationalGates()
     // The required production site replaces the observational MPA build; both

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików