@vzn/vx 0.0.651 → 0.0.653

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -4
  2. package/launcher.sh +2 -1
  3. package/package.json +5 -5
  4. package/schemas/error.json +1 -1
  5. package/schemas/show.json +15 -20
  6. package/skills/vx/SKILL.md +3 -3
  7. package/src/bin.ts +37 -3
  8. package/src/cache/archive.ts +197 -32
  9. package/src/cache/cache.ts +882 -230
  10. package/src/cache/checkpointer.ts +102 -0
  11. package/src/cache/file-hashes.ts +2 -1
  12. package/src/cache/fold-memo.ts +24 -4
  13. package/src/cache/fs-clock.ts +26 -0
  14. package/src/cache/git-inputs.ts +188 -22
  15. package/src/cache/history-stream.ts +151 -0
  16. package/src/cache/history-writer.ts +76 -0
  17. package/src/cache/index.ts +12 -1
  18. package/src/cache/inputs.ts +616 -135
  19. package/src/cache/key-fold.ts +1 -1
  20. package/src/cache/layer.ts +29 -16
  21. package/src/cache/layered-cache.ts +28 -7
  22. package/src/cache/lockfile-memo.ts +2 -1
  23. package/src/cache/output-index.ts +106 -44
  24. package/src/cache/plugin-cache.ts +2 -2
  25. package/src/cache/policy.ts +6 -2
  26. package/src/cache/remote-lane.ts +3 -1
  27. package/src/cache/run-history.ts +94 -25
  28. package/src/cache/schema.ts +46 -10
  29. package/src/cli/cache.ts +149 -36
  30. package/src/cli/completions.ts +2 -2
  31. package/src/cli/core-alias.ts +9 -2
  32. package/src/cli/docs.ts +4 -2
  33. package/src/cli/help.ts +27 -4
  34. package/src/cli/index.ts +62 -7
  35. package/src/cli/info.ts +5 -4
  36. package/src/cli/init.ts +55 -20
  37. package/src/cli/last.ts +218 -43
  38. package/src/cli/lock.ts +187 -24
  39. package/src/cli/plan-format.ts +24 -21
  40. package/src/cli/plugin-commands.ts +6 -1
  41. package/src/cli/plugin-templates.ts +36 -45
  42. package/src/cli/run.ts +68 -34
  43. package/src/cli/select.ts +160 -64
  44. package/src/cli/show.ts +70 -28
  45. package/src/cli/task-verb.ts +12 -7
  46. package/src/cli/upgrade.ts +210 -76
  47. package/src/cli/watch-filter.ts +88 -15
  48. package/src/cli/watch-fs.ts +34 -47
  49. package/src/cli/watch-judge.ts +73 -9
  50. package/src/cli/watch-set.ts +10 -8
  51. package/src/cli/watch.ts +190 -33
  52. package/src/cli/why.ts +107 -33
  53. package/src/cli/workspace-config.ts +11 -1
  54. package/src/config.ts +56 -30
  55. package/src/exec/env.ts +3 -1
  56. package/src/exec/executor.ts +116 -16
  57. package/src/exec/index.ts +1 -0
  58. package/src/exec/kill-tree.ts +7 -7
  59. package/src/exec/runner.ts +270 -108
  60. package/src/exec/sandbox-binds.ts +109 -18
  61. package/src/exec/sandbox-paths.ts +3 -1
  62. package/src/exec/sandbox-runtime.ts +446 -64
  63. package/src/exec/sandbox-violations.ts +250 -46
  64. package/src/exec/spawn-off.ts +10 -5
  65. package/src/exec/task-proxy.ts +93 -0
  66. package/src/exec/warm-shell.ts +29 -7
  67. package/src/graph/definition.ts +16 -3
  68. package/src/graph/folds.ts +89 -16
  69. package/src/graph/index.ts +8 -0
  70. package/src/graph/priorities.ts +8 -5
  71. package/src/graph/scheduler.ts +176 -39
  72. package/src/graph/stand-ins.ts +36 -0
  73. package/src/graph/task-graph.ts +347 -160
  74. package/src/graph/task-status.ts +5 -1
  75. package/src/handoff.ts +45 -10
  76. package/src/history/failure-mode.ts +39 -6
  77. package/src/history/history.ts +103 -30
  78. package/src/history/index.ts +2 -0
  79. package/src/history/metrics.ts +193 -73
  80. package/src/index.ts +6 -3
  81. package/src/orchestrator/admission.ts +14 -10
  82. package/src/orchestrator/affected-tasks.ts +35 -13
  83. package/src/orchestrator/command-windows.ts +8 -5
  84. package/src/orchestrator/deferred-outputs.ts +42 -13
  85. package/src/orchestrator/doctor.ts +21 -9
  86. package/src/orchestrator/download-policy.ts +8 -10
  87. package/src/orchestrator/excluded-keys.ts +15 -3
  88. package/src/orchestrator/execute-args.ts +9 -2
  89. package/src/orchestrator/execute-task.ts +612 -138
  90. package/src/orchestrator/graph-memo.ts +26 -4
  91. package/src/orchestrator/graph-stage.ts +27 -4
  92. package/src/orchestrator/held-servers.ts +36 -14
  93. package/src/orchestrator/hit-restore.ts +141 -93
  94. package/src/orchestrator/index.ts +4 -1
  95. package/src/orchestrator/keyed-projects.ts +53 -0
  96. package/src/orchestrator/local-floor.ts +21 -3
  97. package/src/orchestrator/local-shortcircuit.ts +2 -2
  98. package/src/orchestrator/lockfile-claim.ts +30 -4
  99. package/src/orchestrator/miss-save.ts +60 -61
  100. package/src/orchestrator/options.ts +13 -0
  101. package/src/orchestrator/output-host.ts +16 -4
  102. package/src/orchestrator/output-log.ts +1 -54
  103. package/src/orchestrator/persistent-task.ts +32 -16
  104. package/src/orchestrator/persistent.ts +152 -15
  105. package/src/orchestrator/placement.ts +82 -16
  106. package/src/orchestrator/plan.ts +39 -17
  107. package/src/orchestrator/plugin-host.ts +238 -110
  108. package/src/orchestrator/plugin.ts +59 -27
  109. package/src/orchestrator/prepare.ts +194 -38
  110. package/src/orchestrator/projects.ts +89 -10
  111. package/src/orchestrator/read-only.ts +115 -0
  112. package/src/orchestrator/remote-prefetch.ts +1 -1
  113. package/src/orchestrator/run-context.ts +6 -2
  114. package/src/orchestrator/run-failures.ts +101 -23
  115. package/src/orchestrator/run-id.ts +13 -2
  116. package/src/orchestrator/run-keys.ts +87 -28
  117. package/src/orchestrator/run-live.ts +16 -13
  118. package/src/orchestrator/run-lock.ts +27 -4
  119. package/src/orchestrator/run-records.ts +95 -74
  120. package/src/orchestrator/run-report.ts +3 -2
  121. package/src/orchestrator/run-stop.ts +17 -9
  122. package/src/orchestrator/run.ts +531 -122
  123. package/src/orchestrator/sandbox-fix.ts +40 -12
  124. package/src/orchestrator/sandbox-request.ts +314 -44
  125. package/src/orchestrator/save-lane.ts +24 -13
  126. package/src/orchestrator/shell-verdict.ts +29 -1
  127. package/src/orchestrator/signals.ts +103 -8
  128. package/src/orchestrator/stable-keys.ts +34 -27
  129. package/src/orchestrator/task-artifacts.ts +16 -3
  130. package/src/orchestrator/task-hash.ts +191 -34
  131. package/src/orchestrator/telemetry-host.ts +25 -6
  132. package/src/orchestrator/telemetry.ts +85 -13
  133. package/src/orchestrator/upstream.ts +3 -1
  134. package/src/report/events.ts +23 -6
  135. package/src/report/forecast.ts +103 -37
  136. package/src/report/framed-output.ts +179 -162
  137. package/src/report/index.ts +2 -1
  138. package/src/report/logger.ts +108 -31
  139. package/src/report/plain-output.ts +15 -9
  140. package/src/report/status-line.ts +28 -55
  141. package/src/report/summary.ts +61 -18
  142. package/src/settings.ts +50 -14
  143. package/src/ui/components.ts +36 -42
  144. package/src/ui/index.ts +3 -1
  145. package/src/ui/live-region.ts +67 -8
  146. package/src/ui/terminal.ts +12 -0
  147. package/src/ui/theme.ts +16 -1
  148. package/src/util/config-frame.ts +142 -6
  149. package/src/util/edit-distance.ts +17 -1
  150. package/src/util/errors.ts +23 -7
  151. package/src/util/foreign-cache-dir.ts +60 -0
  152. package/src/util/git-ignored.ts +59 -5
  153. package/src/util/index.ts +15 -1
  154. package/src/util/paths.ts +51 -7
  155. package/src/util/plain-text.ts +19 -0
  156. package/src/util/secret-mask.ts +57 -1
  157. package/src/util/settle.ts +18 -1
  158. package/src/util/size.ts +12 -2
  159. package/src/util/tail.ts +4 -1
  160. package/src/util/write-file.ts +86 -9
  161. package/src/workspace/affected.ts +225 -27
  162. package/src/workspace/bracket-literal.ts +2 -2
  163. package/src/workspace/config-bytecode.ts +23 -4
  164. package/src/workspace/config-eval.ts +90 -16
  165. package/src/workspace/config-imports.ts +96 -41
  166. package/src/workspace/config-schema.ts +192 -86
  167. package/src/workspace/dependencies-lock.ts +7 -14
  168. package/src/workspace/filter.ts +18 -2
  169. package/src/workspace/index.ts +3 -1
  170. package/src/workspace/json-data.ts +34 -8
  171. package/src/workspace/lockfile.ts +86 -37
  172. package/src/workspace/migration.ts +58 -31
  173. package/src/workspace/module-state.ts +62 -0
  174. package/src/workspace/nested-dirs.ts +8 -0
  175. package/src/workspace/project-loader.ts +276 -32
  176. package/src/workspace/repo-id.ts +29 -6
  177. package/src/workspace/workspace.ts +190 -30
@@ -43,6 +43,16 @@ export interface ExecuteSandbox {
43
43
  * machine's paths and platform, hands `sandboxRequestFor`.
44
44
  */
45
45
  readonly keyed?: readonly string[]
46
+ /**
47
+ * The dependencies' outputs it may read (absolute), which an executor
48
+ * that builds the request again hands `sandboxRequestFor` too.
49
+ */
50
+ readonly dependencyReads?: readonly string[]
51
+ /**
52
+ * The heads of those outputs (absolute): a refused read under one is a
53
+ * file beside them, reported. Handed `sandboxRequestFor` with them.
54
+ */
55
+ readonly dependencyReported?: readonly string[]
46
56
  }
47
57
 
48
58
  /** One declared input file, as the cache key saw it. */
@@ -74,8 +84,9 @@ export interface TaskInputs {
74
84
  * Dependencies whose cache keys are folded. `outputs` lists each one's
75
85
  * declared output files (workspace-relative POSIX, as stored with its cache
76
86
  * entry) — restored on disk before this task runs, so an input-shipping
77
- * executor can include them in the input root. Empty when the upstream's
78
- * entry is not in the local index (a non-cacheable dependency).
87
+ * executor can include them in the input root. A non-cacheable
88
+ * dependency's are its declared outputs found on disk; a cacheable one
89
+ * with no entry in the local index lists none.
79
90
  */
80
91
  readonly upstream: ReadonlyArray<{
81
92
  readonly taskId: string
@@ -104,11 +115,17 @@ export interface ServerSpec {
104
115
  readonly sandbox?: { readonly config: SandboxConfig; readonly nested: readonly string[] }
105
116
  }
106
117
 
118
+ /**
119
+ * One task's command, as core hands it to an executor. A plugin
120
+ * executor's `outputs`, `envDefine` and `forwardArgs` are frozen copies:
121
+ * core saves the outputs after the command, and a config or the run's
122
+ * args are shared with other tasks.
123
+ */
107
124
  export interface ExecuteRequest {
108
125
  /** `${project}#${task}` — for executors that route or log by task. */
109
126
  readonly taskId: string
110
127
  readonly workspaceRoot: string
111
- /** Present for cacheable tasks (the miss path); absent when the task declares no `cache`. */
128
+ /** Present for cacheable tasks (the miss path); absent for a task without `cache: true`. */
112
129
  readonly inputs?: TaskInputs
113
130
  /**
114
131
  * The task's cache key (present when the task is cacheable). An executor
@@ -145,6 +162,18 @@ export interface ExecuteRequest {
145
162
  * executor's `platforms`: its key includes it. Absent when the task names none.
146
163
  */
147
164
  readonly platform?: string
165
+ /**
166
+ * The worker type to run it on, one of the executor's `workers`: the
167
+ * task's `exec.worker`, else the first type with one of its platforms.
168
+ * Absent when the executor names no types.
169
+ */
170
+ readonly worker?: string
171
+ /**
172
+ * Which attempt of the task this is, from 2 when it retries; absent on
173
+ * the first. An executor that can, runs a retry somewhere other than
174
+ * the attempt before it, so the machine is not the flake.
175
+ */
176
+ readonly attempt?: number
148
177
  /**
149
178
  * Whether this task's outputs must land on the submitter's disk now.
150
179
  * 'eager' (or absent) = materialise as today. 'deferred' = leave them
@@ -179,7 +208,8 @@ export interface ExecuteRequest {
179
208
  readonly onStderr: (chunk: string) => void
180
209
  /**
181
210
  * Aborted when the run stops (Ctrl-C, SIGTERM, SIGHUP or an embedder's
182
- * `RunOptions.signal`) or when `timeoutMs` elapses. An executor ends its
211
+ * `RunOptions.signal`), when `timeoutMs` elapses, or when fail-fast
212
+ * withdraws a task `running` says is not begun. An executor ends its
183
213
  * work and returns: a child an executor spawned is not one core can
184
214
  * reach (H-10, H-12). One already aborted is work not to start (B-55).
185
215
  * Any exit after the timeout's abort, a 0 included, is a timeout. Core
@@ -225,10 +255,11 @@ export interface ExecuteResult extends RunResult {
225
255
  */
226
256
  readonly outputs?: { kind: 'disk' } | { kind: 'deferred'; materialize: () => Promise<void> }
227
257
  /**
228
- * Tracked files the command changed where it ran, workspace-relative
229
- * POSIX: an executor that runs it elsewhere says so, as this disk shows
230
- * none of it. One the key read withholds the save under the key, as an
231
- * input changed here does.
258
+ * Tracked files the command changed where it ran, and untracked ones it
259
+ * found there, workspace-relative POSIX: an executor that runs it
260
+ * elsewhere says so, as this disk shows none of it. One the key read, or
261
+ * one its inputs declare, withholds the save under the key, as an input
262
+ * changed here does.
232
263
  */
233
264
  readonly changed?: readonly string[]
234
265
  /**
@@ -280,9 +311,12 @@ export interface TaskExecutor {
280
311
  /** Runs the command somewhere else. Never offered a `pinnedLocal` task. */
281
312
  readonly remote?: boolean
282
313
  /**
283
- * A `remote` executor that enforces `ExecuteRequest.sandbox` where it
284
- * runs the command, as the local one does. It is offered sandboxed tasks
285
- * and their dependants; the request's violations come back on the result.
314
+ * Enforces `ExecuteRequest.sandbox` where it runs the command, as the
315
+ * local one does; the request's violations come back on the result. Only
316
+ * such an executor is offered a task that declares `exec.sandbox`, here
317
+ * or remote: one that ran it unsandboxed passed a boundary it never
318
+ * checked and cached the output. A `remote` one is offered the
319
+ * dependants of a sandboxed task too.
286
320
  */
287
321
  readonly sandbox?: boolean
288
322
  /**
@@ -327,6 +361,14 @@ export interface TaskExecutor {
327
361
  * declares none is never offered such a task off this machine.
328
362
  */
329
363
  readonly platforms?: readonly string[]
364
+ /**
365
+ * Its worker types in order of preference, each with the platforms its
366
+ * machines run (`@vzn/vx-dte`'s `workers`). A task naming `exec.worker`
367
+ * is offered it only when it has that type on one of the task's
368
+ * platforms; any other task goes to the first type that fits, on
369
+ * `ExecuteRequest.worker`. Its `platforms` must list every one of them.
370
+ */
371
+ readonly workers?: ReadonlyArray<readonly [name: string, platforms: readonly string[]]>
330
372
  /**
331
373
  * How many tasks this executor runs at once. Its tasks then occupy a pool
332
374
  * of this size instead of the local worker slots, so a remote pool is not
@@ -334,7 +376,11 @@ export interface TaskExecutor {
334
376
  * is refused when the executor is resolved. Absent = the local pool.
335
377
  */
336
378
  readonly capacity?: number
337
- /** Per-task opt-out at placement time. Absent = accepts every task it is offered. */
379
+ /**
380
+ * Per-task opt-out at placement time. Absent = accepts every task it is
381
+ * offered. `false` or `undefined` declines; any other non-boolean is
382
+ * refused, naming the plugin.
383
+ */
338
384
  accepts?(task: TaskPlacement): boolean
339
385
  /**
340
386
  * The tasks placed here that will run and have NOT finished yet — called
@@ -377,6 +423,13 @@ export interface TaskExecutor {
377
423
  * anything but a finite number, reports none.
378
424
  */
379
425
  idealMs?(): number | undefined
426
+ /**
427
+ * The workers that would have run the run best, judged after it from
428
+ * what each task took: for the run summary, so its reader can change
429
+ * how many workers the executor may start and of what size. Asked once
430
+ * at the end, only when a sink is on. A throw reports none.
431
+ */
432
+ advice?(): readonly WorkerAdvice[]
380
433
  /**
381
434
  * Its own Chrome-trace events for `vx run --profile` (the machines it ran
382
435
  * tasks on, in time), asked once at the end, only when the flag is set.
@@ -388,8 +441,17 @@ export interface TaskExecutor {
388
441
  * Where a task it was handed runs (`small#2`), for the live row; asked
389
442
  * per frame. `null` while it holds the task for a machine to begin it:
390
443
  * the row says `waiting`, and its clock starts when a machine begins it.
444
+ * Under `--continue=never` a failure aborts the `signal` of each task it
445
+ * answers `null` for, and the task is skipped, never started.
391
446
  */
392
447
  running?(taskId: string): string | null | undefined
448
+ /**
449
+ * A `servers` executor's: no task left in the run uses persistent task
450
+ * `taskId`, so each copy it started beside a dependant stops now, not at
451
+ * teardown. Core asks once, as the run stops its own copy (`schema.md`,
452
+ * "Stopped once nothing needs it"). A throw is ignored.
453
+ */
454
+ unneeded?(taskId: string): void
393
455
  execute(req: ExecuteRequest): Promise<ExecuteResult>
394
456
  }
395
457
 
@@ -413,7 +475,11 @@ export interface MachineReport {
413
475
  /** Bytes in use at once, at the most and on average. */
414
476
  memoryPeakBytes: number
415
477
  memoryAvgBytes: number
416
- /** Core-time its tasks used over the core-time it had, 0 to 1. */
478
+ /**
479
+ * Its machine's busy core-time over the core-time it had, from its first
480
+ * task's send to its last one's end, 0 to 1 (its tasks' own where its
481
+ * machine said none): whether cores were left idle.
482
+ */
417
483
  utilization: number
418
484
  /** Cores the executor booked for the tasks it ran, on average over its time online. */
419
485
  bookedAvg?: number
@@ -423,6 +489,27 @@ export interface MachineReport {
423
489
  startMs?: number
424
490
  }
425
491
 
492
+ /**
493
+ * Workers that would have run the run best (`TaskExecutor.advice`): `of`
494
+ * says on what. `declared`: as many of each size the executor may start
495
+ * as end it soonest; `more`: past each size's limit, when that ends it
496
+ * sooner; `any`: one shape a platform, of any cores, ending as soon for
497
+ * the least core time.
498
+ */
499
+ export interface WorkerAdvice {
500
+ of: 'declared' | 'more' | 'any'
501
+ /** The run's end on them, from its start. */
502
+ endMs: number
503
+ workers: readonly {
504
+ count: number
505
+ cores: number
506
+ memoryBytes: number
507
+ platform?: string
508
+ /** The size it is, for `declared` and `more`. */
509
+ size?: string
510
+ }[]
511
+ }
512
+
426
513
  /**
427
514
  * What a remote executor rejects with when it gives a task back: core runs
428
515
  * the same request on the local floor and says `reason` once (a remote that
@@ -535,8 +622,8 @@ export function assertExecuteResult(
535
622
 
536
623
  /**
537
624
  * Place one task: the first executor, in declaration order, that may take
538
- * it — a `remote` executor is skipped for a `pinnedLocal` task, then
539
- * `accepts()` decides. Decided once per task before scheduling. The local
625
+ * it — a `remote` executor is skipped for a `pinnedLocal` task, any one
626
+ * `barred` names is skipped, then `accepts()` decides. Decided once per task before scheduling. The local
540
627
  * executor accepts everything, so with it declared the only throw is an
541
628
  * `accepts()` that threw: refused as `label` names that executor, where it
542
629
  * surfaced as a bare stack naming no plugin (item 1022).
@@ -547,10 +634,12 @@ export function selectExecutor(
547
634
  label: (executor: TaskExecutor) => string = (e) => `executor '${e.name}'`,
548
635
  /** `task.pinnedLocal` as `executor` sees it: one that enforces the sandbox or carries servers lifts some pins. */
549
636
  pinnedFor: (executor: TaskExecutor) => boolean = () => task.pinnedLocal,
637
+ /** Never offered the task: a sandboxed one is barred from an executor that does not enforce it. */
638
+ barred: (executor: TaskExecutor) => boolean = () => false,
550
639
  ): TaskExecutor {
551
640
  for (const executor of executors) {
552
641
  const pinned = pinnedFor(executor)
553
- if (pinned && executor.remote === true) continue
642
+ if ((pinned && executor.remote === true) || barred(executor)) continue
554
643
  if (executor.accepts === undefined) return executor
555
644
  let taken: unknown
556
645
  try {
@@ -563,6 +652,17 @@ export function selectExecutor(
563
652
  taken.catch(() => {})
564
653
  throw new Error('returned a Promise; accepts is synchronous')
565
654
  }
655
+ // `'no'` was truthy and took the task; admit refuses the same answer.
656
+ if (taken !== undefined && typeof taken !== 'boolean') {
657
+ const t = typeof taken
658
+ const what =
659
+ taken === null
660
+ ? 'null'
661
+ : Array.isArray(taken)
662
+ ? 'an array'
663
+ : `${t === 'object' ? 'an' : 'a'} ${t}`
664
+ throw new Error(`returned ${what}, not true or false`)
665
+ }
566
666
  } catch (err) {
567
667
  const m = thrownMessage(err)
568
668
  throw new UserError(`${label(executor)} failed in accepts for ${task.taskId}: ${m}`)
package/src/exec/index.ts CHANGED
@@ -64,6 +64,7 @@ export {
64
64
  type ExecuteSandbox,
65
65
  type InputFile,
66
66
  type MachineReport,
67
+ type WorkerAdvice,
67
68
  type ServerSpec,
68
69
  type TaskExecutor,
69
70
  type TaskInputs,
@@ -151,7 +151,7 @@ function stopGuard(): void {
151
151
 
152
152
  function guardWrite(line: string): void {
153
153
  if (typeof guardFd !== 'number') return
154
- let deadline = Date.now() + GUARD_STALL_MS
154
+ let deadline = performance.now() + GUARD_STALL_MS
155
155
  // A turn's batch can be more than the queue takes at once: it goes in
156
156
  // pieces as the guard drains it, and only a guard that takes nothing for
157
157
  // the whole stall is gone. With one deadline for the batch, 2000 strikes
@@ -162,9 +162,9 @@ function guardWrite(line: string): void {
162
162
  const n = writeSync(guardFd, bytes, 0, Math.min(bytes.length, GUARD_PIECE))
163
163
  if (n === bytes.length) return
164
164
  bytes = bytes.subarray(n)
165
- deadline = Date.now() + GUARD_STALL_MS
165
+ deadline = performance.now() + GUARD_STALL_MS
166
166
  } catch (err) {
167
- if ((err as NodeJS.ErrnoException).code !== 'EAGAIN' || Date.now() >= deadline) break
167
+ if ((err as NodeJS.ErrnoException).code !== 'EAGAIN' || performance.now() >= deadline) break
168
168
  Bun.sleepSync(1)
169
169
  }
170
170
  }
@@ -414,7 +414,7 @@ export async function untilGroupsGone(
414
414
  children: readonly Child[],
415
415
  graceMs: number,
416
416
  ): Promise<Child[]> {
417
- const deadline = Date.now() + graceMs
417
+ const deadline = performance.now() + graceMs
418
418
  let timer: ReturnType<typeof setTimeout> | undefined
419
419
  // Not unref'd: it is what guarantees progress when every other handle
420
420
  // has drained, and the grace is bounded either way.
@@ -426,8 +426,8 @@ export async function untilGroupsGone(
426
426
  ])
427
427
  if (timer !== undefined) clearTimeout(timer)
428
428
  let left = children.filter(groupAlive)
429
- while (left.length > 0 && Date.now() < deadline) {
430
- await Bun.sleep(Math.min(GROUP_POLL_MS, deadline - Date.now()))
429
+ while (left.length > 0 && performance.now() < deadline) {
430
+ await Bun.sleep(Math.min(GROUP_POLL_MS, deadline - performance.now()))
431
431
  left = left.filter(groupAlive)
432
432
  }
433
433
  return left
@@ -443,7 +443,7 @@ export async function untilGroupsGone(
443
443
  * and a group that ignored the SIGTERM went un-killed; there the group
444
444
  * signal is the answer and the grace ends the wait.
445
445
  */
446
- function groupAlive(child: Child): boolean {
446
+ export function groupAlive(child: Child): boolean {
447
447
  if (!(child.pid > 0) || goneGroups.has(child)) return false
448
448
  try {
449
449
  process.kill(-child.pid, 0)