@vzn/vx 0.0.600 → 0.0.601
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.601",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.601",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.601",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.601",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.601"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/exec/runner.ts
CHANGED
|
@@ -867,8 +867,8 @@ export function runPersistent(opts: PersistentOptions): PersistentSpawn {
|
|
|
867
867
|
// Wire up readers. We deliberately don't await them — they run for
|
|
868
868
|
// the child's lifetime. The `ready` promise resolves out-of-band.
|
|
869
869
|
const [stdout, stderr] = pipes.streams(child)
|
|
870
|
-
|
|
871
|
-
pipes.close(),
|
|
870
|
+
const readers = Promise.all([consumeChunks(stdout, false), consumeChunks(stderr, true)]).finally(
|
|
871
|
+
() => pipes.close(),
|
|
872
872
|
)
|
|
873
873
|
|
|
874
874
|
opts.liveChildren?.add(child)
|
|
@@ -915,10 +915,19 @@ export function runPersistent(opts: PersistentOptions): PersistentSpawn {
|
|
|
915
915
|
|
|
916
916
|
// If the child exits BEFORE ready fires, that's a failure to start
|
|
917
917
|
// — reject the ready promise so the caller can surface it.
|
|
918
|
-
void child.exited.then((code) => {
|
|
918
|
+
void child.exited.then(async (code) => {
|
|
919
919
|
opts.liveChildren?.delete(child)
|
|
920
920
|
releaseServerGroup(child)
|
|
921
921
|
if (readyTimer !== undefined) clearTimeout(readyTimer)
|
|
922
|
+
// The exit can land before the readers took the marker it printed on
|
|
923
|
+
// the way out: `echo READY; exit` read as never ready under load. The
|
|
924
|
+
// drain is bounded, as a task's is, for a grandchild holding the pipe.
|
|
925
|
+
if (readyAt === undefined && gaveUpAt === undefined) {
|
|
926
|
+
await Promise.race([
|
|
927
|
+
readers,
|
|
928
|
+
new Promise((resolve) => setTimeout(resolve, POST_EXIT_DRAIN_MS).unref?.()),
|
|
929
|
+
])
|
|
930
|
+
}
|
|
922
931
|
if (readyAt === undefined && gaveUpAt === undefined) {
|
|
923
932
|
rejectReady(
|
|
924
933
|
new PersistentReadyError(
|
|
@@ -22,10 +22,13 @@ export type FailureMode = 'stable' | 'flaky-recoverable' | 'flaky-fatal'
|
|
|
22
22
|
/**
|
|
23
23
|
* Per-key outcome projection: every keyed (project, task, hash) under
|
|
24
24
|
* `source` (`runs`, or a relation with its `id`, `project`, `task`, `hash`,
|
|
25
|
-
* `status`, `cache_hit` columns) with how often it failed, how often it
|
|
25
|
+
* `status`, `cache_hit`, `cached` columns) with how often it failed, how often it
|
|
26
26
|
* passed — an executed success or a cache hit, which replays a success —
|
|
27
|
-
* and how often it failed AFTER its first pass (`relapses`).
|
|
28
|
-
*
|
|
27
|
+
* and how often it failed AFTER its first pass (`relapses`). A task with
|
|
28
|
+
* no cache block keys on its config alone and runs every time, so its key
|
|
29
|
+
* says nothing about its inputs: no reader counts it, as no run's row
|
|
30
|
+
* calls it flaky. Every flakiness reader is a filter over this one
|
|
31
|
+
* projection.
|
|
29
32
|
*/
|
|
30
33
|
function keyOutcomesSql(source: string, where = ''): string {
|
|
31
34
|
return `SELECT project, task, hash, SUM(failed) AS failures, SUM(passed) AS passes,
|
|
@@ -39,7 +42,7 @@ function keyOutcomesSql(source: string, where = ''): string {
|
|
|
39
42
|
CASE WHEN status = 'success' OR status IN ${HIT_STATUSES} OR cache_hit = 1
|
|
40
43
|
THEN 1 ELSE 0 END AS passed
|
|
41
44
|
FROM ${source}
|
|
42
|
-
WHERE ${KEYED_RUNS_SQL}${where}))
|
|
45
|
+
WHERE ${KEYED_RUNS_SQL} AND cached IS NOT 0${where}))
|
|
43
46
|
GROUP BY project, task, hash`
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -14,6 +14,8 @@ import { isCacheHit, taskTelemetryOf, type TaskTelemetry } from './telemetry.js'
|
|
|
14
14
|
|
|
15
15
|
export interface RunRecordsInput {
|
|
16
16
|
outcomes: readonly TaskOutcome[]
|
|
17
|
+
/** Tasks that ran behind a failed dependency (`--continue=always`). */
|
|
18
|
+
tainted?: ReadonlySet<string>
|
|
17
19
|
runId: string
|
|
18
20
|
/** Wall-clock ms at run start; every per-task ns offset is anchored to it. */
|
|
19
21
|
startedAtMs: number
|
|
@@ -66,7 +68,11 @@ export function assembleRunRecords(input: RunRecordsInput): RunRecords {
|
|
|
66
68
|
if (o.status === 'aborted') continue
|
|
67
69
|
if (input.withTelemetry) telemetryTasks.push(taskTelemetryOf(o))
|
|
68
70
|
runs.push({
|
|
69
|
-
|
|
71
|
+
// A failure behind a failed dependency is recorded keyless (`''`, which
|
|
72
|
+
// the key readers skip): the flaky list asks a key whether it failed.
|
|
73
|
+
...(o.hash !== undefined && !(o.status === 'failed' && input.tainted?.has(o.node.id) === true)
|
|
74
|
+
? { hash: o.hash }
|
|
75
|
+
: {}),
|
|
70
76
|
project: o.node.projectName,
|
|
71
77
|
task: o.node.taskName,
|
|
72
78
|
status: o.status,
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -939,6 +939,7 @@ async function runOnBus(
|
|
|
939
939
|
// dispatch, and the tracker carries it to what is built on it.
|
|
940
940
|
const taintSeeds = new Set(excluded.seeds)
|
|
941
941
|
const taint = taintTracker(options.continueMode === 'always', taintSeeds, nodes)
|
|
942
|
+
const taintedRan = new Set<string>()
|
|
942
943
|
const dependedOn = new Set<string>()
|
|
943
944
|
for (const n of nodes.values()) for (const d of n.deps) dependedOn.add(d)
|
|
944
945
|
|
|
@@ -959,6 +960,7 @@ async function runOnBus(
|
|
|
959
960
|
)
|
|
960
961
|
taintSeeds.add(node.id)
|
|
961
962
|
const tainted = taint.judge(node, upstream)
|
|
963
|
+
if (tainted) taintedRan.add(node.id)
|
|
962
964
|
return {
|
|
963
965
|
node,
|
|
964
966
|
upstream,
|
|
@@ -1027,7 +1029,7 @@ async function runOnBus(
|
|
|
1027
1029
|
const historyDb = prepared.localCache.dbHandle()
|
|
1028
1030
|
const cacheOff = cachesNothing(policy)
|
|
1029
1031
|
const judgeFlaky = (o: TaskOutcome): void => {
|
|
1030
|
-
const candidates = flakyCandidates([o])
|
|
1032
|
+
const candidates = flakyCandidates([o], taintedRan)
|
|
1031
1033
|
if (candidates.length === 0) return
|
|
1032
1034
|
try {
|
|
1033
1035
|
const found = detectFlaky(historyDb, candidates)[0]
|
|
@@ -1212,6 +1214,7 @@ async function runOnBus(
|
|
|
1212
1214
|
const recordsOf = (final: TaskOutcome[], runOk: boolean) =>
|
|
1213
1215
|
assembleRunRecords({
|
|
1214
1216
|
outcomes: final,
|
|
1217
|
+
tainted: taintedRan,
|
|
1215
1218
|
runId,
|
|
1216
1219
|
startedAtMs: endedAtMsAtStart,
|
|
1217
1220
|
endedAtMs,
|
|
@@ -1759,7 +1762,10 @@ export function projectNamed(
|
|
|
1759
1762
|
}
|
|
1760
1763
|
|
|
1761
1764
|
/** The executed, keyed outcomes of a run — what flakiness is judged on. */
|
|
1762
|
-
function flakyCandidates(
|
|
1765
|
+
function flakyCandidates(
|
|
1766
|
+
outcomes: readonly TaskOutcome[],
|
|
1767
|
+
tainted: ReadonlySet<string>,
|
|
1768
|
+
): FlakyCandidate[] {
|
|
1763
1769
|
const out: FlakyCandidate[] = []
|
|
1764
1770
|
for (const o of outcomes) {
|
|
1765
1771
|
if (o.status !== 'success' && o.status !== 'failed') continue
|
|
@@ -1768,6 +1774,9 @@ function flakyCandidates(outcomes: readonly TaskOutcome[]): FlakyCandidate[] {
|
|
|
1768
1774
|
// and runs every time, so one bad network day would read as a flake for
|
|
1769
1775
|
// thirty days. Groups do no work.
|
|
1770
1776
|
if (o.hash === undefined || o.node.config.cache === undefined || isGroupTask(o.node)) continue
|
|
1777
|
+
// Behind a failed dependency the failure is the dependency's: the key it
|
|
1778
|
+
// had passed on read as a flake for thirty days.
|
|
1779
|
+
if (o.status === 'failed' && tainted.has(o.node.id)) continue
|
|
1771
1780
|
out.push({
|
|
1772
1781
|
project: o.node.projectName,
|
|
1773
1782
|
task: o.node.taskName,
|