ttsc 0.31.3 → 0.31.5
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/lib/compiler/internal/build/BuildExecution.js +43 -6
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/build/runExternalEmitProvenance.d.ts +15 -0
- package/lib/compiler/internal/build/runExternalEmitProvenance.js +67 -4
- package/lib/compiler/internal/build/runExternalEmitProvenance.js.map +1 -1
- package/lib/internal/E2ETrace.d.ts +1 -0
- package/lib/internal/E2ETrace.js.map +1 -1
- package/lib/internal/probeLocalProcess.d.ts +31 -0
- package/lib/internal/probeLocalProcess.js +64 -0
- package/lib/internal/probeLocalProcess.js.map +1 -0
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.d.ts +2 -0
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js +2 -1
- package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js.map +1 -1
- package/lib/launcher/internal/runtime/isLocalProcessGone.d.ts +10 -10
- package/lib/launcher/internal/runtime/isLocalProcessGone.js +15 -22
- package/lib/launcher/internal/runtime/isLocalProcessGone.js.map +1 -1
- package/lib/launcher/internal/watch/FseventsStreams.d.ts +2 -0
- package/lib/launcher/internal/watch/FseventsStreams.js +19 -0
- package/lib/launcher/internal/watch/FseventsStreams.js.map +1 -1
- package/lib/plugin/internal/load/loadProjectPlugins.js +11 -5
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/source/GoBuildCacheCoordination.js +4 -1
- package/lib/plugin/internal/source/GoBuildCacheCoordination.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildLockOwner.d.ts +8 -7
- package/lib/plugin/internal/source/PluginBuildLockOwner.js +10 -14
- package/lib/plugin/internal/source/PluginBuildLockOwner.js.map +1 -1
- package/package.json +8 -8
- package/src/compiler/internal/build/BuildExecution.ts +43 -10
- package/src/compiler/internal/build/runExternalEmitProvenance.ts +84 -4
- package/src/internal/E2ETrace.ts +2 -0
- package/src/internal/probeLocalProcess.ts +61 -0
- package/src/launcher/internal/runtime/ProcessOwnedDirectory.ts +5 -1
- package/src/launcher/internal/runtime/isLocalProcessGone.ts +17 -20
- package/src/launcher/internal/watch/FseventsStreams.ts +22 -0
- package/src/plugin/internal/load/loadProjectPlugins.ts +16 -13
- package/src/plugin/internal/source/GoBuildCacheCoordination.ts +4 -1
- package/src/plugin/internal/source/PluginBuildLockOwner.ts +10 -13
|
@@ -92,15 +92,16 @@ export declare namespace PluginBuildLockOwner {
|
|
|
92
92
|
* process with its id exists. A holder on another host, or one whose id is
|
|
93
93
|
* alive (possibly reused), is not proven gone.
|
|
94
94
|
*
|
|
95
|
-
* Only ESRCH
|
|
96
|
-
*
|
|
95
|
+
* Only ESRCH, or a Linux zombie that exited but was never collected, proves
|
|
96
|
+
* absence. Permission errors, invalid probe arguments and other failures
|
|
97
|
+
* remain inconclusive and must not authorize reclamation.
|
|
97
98
|
*
|
|
98
|
-
* @evidence contracts/common.md#principled-implementation
|
|
99
|
-
* @evidence contracts/common.md#clear-and-simple-design Host comparison and
|
|
99
|
+
* @evidence contracts/common.md#principled-implementation probeLocalProcess checks existence without terminating the process; same-host ESRCH or an uncollected zombie establishes absence, while presence or any other error does not.
|
|
100
|
+
* @evidence contracts/common.md#clear-and-simple-design Host comparison and the shared probe express the proof boundary directly, without a second process-discovery mechanism.
|
|
100
101
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Unexpected probe failures are not converted to a dead-owner special case or hidden behind retries.
|
|
101
|
-
* @evidence contracts/common.md#meaningful-documentation Native paragraphs distinguish PID reuse, remote owners and each inconclusive failure class before the tags.
|
|
102
|
-
* @evidence contracts/portability.md#os-neutral-implementation os.hostname and Node's platform-independent signal-zero probe preserve native host scope; only the explicit no-such-process code authorizes absence.
|
|
103
|
-
* @evidence contracts/performance.md#efficient-algorithms A hostname comparison and one process-existence syscall avoid enumerating native processes; string comparison costs grow only with hostname length.
|
|
102
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs distinguish PID reuse, remote owners, uncollected zombies and each inconclusive failure class before the tags.
|
|
103
|
+
* @evidence contracts/portability.md#os-neutral-implementation os.hostname and Node's platform-independent signal-zero probe preserve native host scope; only the explicit no-such-process code, or Linux's own zombie state, authorizes absence.
|
|
104
|
+
* @evidence contracts/performance.md#efficient-algorithms A hostname comparison and one process-existence syscall, plus one /proc read on Linux, avoid enumerating native processes; string comparison costs grow only with hostname length.
|
|
104
105
|
*
|
|
105
106
|
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Liveness is a time-sensitive observation; a previously absent or present PID cannot be cached as proof for a later reclamation.
|
|
106
107
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The query owns no retained handle or running task and returns only its immediate proof decision.
|
|
@@ -7,6 +7,7 @@ exports.PluginBuildLockOwner = void 0;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const probeLocalProcess_1 = require("../../../internal/probeLocalProcess");
|
|
10
11
|
const PluginBuildLockProtocol_1 = require("./PluginBuildLockProtocol");
|
|
11
12
|
/**
|
|
12
13
|
* The holder a plugin build lock generation records, and what can be proven
|
|
@@ -115,15 +116,16 @@ var PluginBuildLockOwner;
|
|
|
115
116
|
* process with its id exists. A holder on another host, or one whose id is
|
|
116
117
|
* alive (possibly reused), is not proven gone.
|
|
117
118
|
*
|
|
118
|
-
* Only ESRCH
|
|
119
|
-
*
|
|
119
|
+
* Only ESRCH, or a Linux zombie that exited but was never collected, proves
|
|
120
|
+
* absence. Permission errors, invalid probe arguments and other failures
|
|
121
|
+
* remain inconclusive and must not authorize reclamation.
|
|
120
122
|
*
|
|
121
|
-
* @evidence contracts/common.md#principled-implementation
|
|
122
|
-
* @evidence contracts/common.md#clear-and-simple-design Host comparison and
|
|
123
|
+
* @evidence contracts/common.md#principled-implementation probeLocalProcess checks existence without terminating the process; same-host ESRCH or an uncollected zombie establishes absence, while presence or any other error does not.
|
|
124
|
+
* @evidence contracts/common.md#clear-and-simple-design Host comparison and the shared probe express the proof boundary directly, without a second process-discovery mechanism.
|
|
123
125
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Unexpected probe failures are not converted to a dead-owner special case or hidden behind retries.
|
|
124
|
-
* @evidence contracts/common.md#meaningful-documentation Native paragraphs distinguish PID reuse, remote owners and each inconclusive failure class before the tags.
|
|
125
|
-
* @evidence contracts/portability.md#os-neutral-implementation os.hostname and Node's platform-independent signal-zero probe preserve native host scope; only the explicit no-such-process code authorizes absence.
|
|
126
|
-
* @evidence contracts/performance.md#efficient-algorithms A hostname comparison and one process-existence syscall avoid enumerating native processes; string comparison costs grow only with hostname length.
|
|
126
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs distinguish PID reuse, remote owners, uncollected zombies and each inconclusive failure class before the tags.
|
|
127
|
+
* @evidence contracts/portability.md#os-neutral-implementation os.hostname and Node's platform-independent signal-zero probe preserve native host scope; only the explicit no-such-process code, or Linux's own zombie state, authorizes absence.
|
|
128
|
+
* @evidence contracts/performance.md#efficient-algorithms A hostname comparison and one process-existence syscall, plus one /proc read on Linux, avoid enumerating native processes; string comparison costs grow only with hostname length.
|
|
127
129
|
*
|
|
128
130
|
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Liveness is a time-sensitive observation; a previously absent or present PID cannot be cached as proof for a later reclamation.
|
|
129
131
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The query owns no retained handle or running task and returns only its immediate proof decision.
|
|
@@ -131,13 +133,7 @@ var PluginBuildLockOwner;
|
|
|
131
133
|
function gone(owner) {
|
|
132
134
|
if (owner.hostname.toLowerCase() !== node_os_1.default.hostname().toLowerCase())
|
|
133
135
|
return false;
|
|
134
|
-
|
|
135
|
-
process.kill(owner.pid, 0);
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
return error.code === "ESRCH";
|
|
140
|
-
}
|
|
136
|
+
return (0, probeLocalProcess_1.probeLocalProcess)(owner.pid).result === "absent";
|
|
141
137
|
}
|
|
142
138
|
PluginBuildLockOwner.gone = gone;
|
|
143
139
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginBuildLockOwner.js","sourceRoot":"","sources":["../../../../src/plugin/internal/source/PluginBuildLockOwner.ts"],"names":[],"mappings":";;;;;;AAAA,sDAAyB;AACzB,sDAAyB;AACzB,0DAA6B;AAE7B,uEAAoE;AAEpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,IAAiB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"PluginBuildLockOwner.js","sourceRoot":"","sources":["../../../../src/plugin/internal/source/PluginBuildLockOwner.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ttsc",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.5",
|
|
4
4
|
"description": "General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
},
|
|
62
62
|
"optionalDependencies": {
|
|
63
63
|
"fsevents": "^2.3.3",
|
|
64
|
-
"@ttsc/linux-arm": "0.31.
|
|
65
|
-
"@ttsc/linux-x64": "0.31.
|
|
66
|
-
"@ttsc/linux-arm64": "0.31.
|
|
67
|
-
"@ttsc/darwin-
|
|
68
|
-
"@ttsc/
|
|
69
|
-
"@ttsc/
|
|
70
|
-
"@ttsc/win32-arm64": "0.31.
|
|
64
|
+
"@ttsc/linux-arm": "0.31.5",
|
|
65
|
+
"@ttsc/linux-x64": "0.31.5",
|
|
66
|
+
"@ttsc/linux-arm64": "0.31.5",
|
|
67
|
+
"@ttsc/darwin-x64": "0.31.5",
|
|
68
|
+
"@ttsc/win32-x64": "0.31.5",
|
|
69
|
+
"@ttsc/darwin-arm64": "0.31.5",
|
|
70
|
+
"@ttsc/win32-arm64": "0.31.5"
|
|
71
71
|
},
|
|
72
72
|
"sideEffects": [
|
|
73
73
|
"./lib/register.js",
|
|
@@ -195,10 +195,16 @@ export namespace BuildExecution {
|
|
|
195
195
|
compilerArgsCwd !== execution.projectRoot &&
|
|
196
196
|
JSON.parse(tsgoArgs).length !== 0
|
|
197
197
|
) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
// WARNING: NEVER reject a host here for not declaring
|
|
199
|
+
// `compilerArgsCwd`. Every native host, a plugin library's own
|
|
200
|
+
// executable such as typia's among them, is built against this
|
|
201
|
+
// installed ttsc's driver (the build replaces
|
|
202
|
+
// github.com/samchon/ttsc/packages/ttsc with it), and that driver's
|
|
203
|
+
// LoadProgram reads this variable beside TTSC_TSGO_ARGS itself. A host
|
|
204
|
+
// that bypasses that fallback ignores the forwarded arguments as a
|
|
205
|
+
// whole, so nothing can resolve against the wrong directory. Libraries
|
|
206
|
+
// published before the capability existed cannot declare it, and
|
|
207
|
+
// rejecting them broke them outright.
|
|
202
208
|
SidecarEnvironment.write(env, TSGO_ARGS_CWD_ENV, compilerArgsCwd);
|
|
203
209
|
} else {
|
|
204
210
|
SidecarEnvironment.write(env, TSGO_ARGS_CWD_ENV, undefined);
|
|
@@ -673,12 +679,39 @@ export namespace BuildExecution {
|
|
|
673
679
|
TsgoArguments.createNativeBuildTsgoArgs(execution, options),
|
|
674
680
|
);
|
|
675
681
|
if (options.forceEmitProvenance !== true) return run();
|
|
676
|
-
if (host.capabilities?.emitProvenance
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
+
if (host.capabilities?.emitProvenance === true)
|
|
683
|
+
return captureNativeEmitProvenance(run);
|
|
684
|
+
// WARNING: NEVER reject a compiler host for lacking `emitProvenance`.
|
|
685
|
+
//
|
|
686
|
+
// Plugin libraries such as typia ship their own Go executable host with its
|
|
687
|
+
// own argv parser, built against whatever ttsc release the user installed.
|
|
688
|
+
// They cannot accept `--emit-provenance-json`, and they will not declare a
|
|
689
|
+
// capability added after they were published. Throwing "does not support
|
|
690
|
+
// required emit provenance" here broke every such library under ttsx and
|
|
691
|
+
// every other provenance-requiring caller at once. A new ttsc requirement
|
|
692
|
+
// must never turn into a hard failure for a host that predates it.
|
|
693
|
+
//
|
|
694
|
+
// Run the host unchanged and observe its ownership from outside instead:
|
|
695
|
+
// the selected tsgo answers the read-only configuration and source-list
|
|
696
|
+
// probes for the same tsconfig, and the outputs whose metadata moved across
|
|
697
|
+
// the run are the host's writes. Layouts those probes cannot cover leave
|
|
698
|
+
// ownership unknown, which the caller reports, exactly as for plain tsgo.
|
|
699
|
+
return runExternalEmitProvenance({
|
|
700
|
+
args: TsgoArguments.createTsgoBuildArgs(execution, options, {
|
|
701
|
+
listEmittedFiles: false,
|
|
702
|
+
}),
|
|
703
|
+
binary: execution.tsgo.binary,
|
|
704
|
+
cwd: execution.compilerSelection.compilerArgsCwd,
|
|
705
|
+
env: compilerEnv(options.env),
|
|
706
|
+
run: () => {
|
|
707
|
+
const result = run();
|
|
708
|
+
return {
|
|
709
|
+
completedNormally: result.processCompletedNormally === true,
|
|
710
|
+
result,
|
|
711
|
+
};
|
|
712
|
+
},
|
|
713
|
+
writes: "observed",
|
|
714
|
+
});
|
|
682
715
|
}
|
|
683
716
|
|
|
684
717
|
/**
|
|
@@ -37,6 +37,12 @@ import { PassthroughFlags } from "./PassthroughFlags";
|
|
|
37
37
|
* supported upstream naming subset; this adapter does not certify arbitrary
|
|
38
38
|
* compiler behavior.
|
|
39
39
|
*
|
|
40
|
+
* A foreign compiler host that cannot report its writes, a plugin library's own
|
|
41
|
+
* executable built before ttsc required provenance, is observed instead: the
|
|
42
|
+
* selected tsgo answers the same read-only probes, the host runs unchanged, and
|
|
43
|
+
* its writes are the predicted outputs whose metadata moved across the run.
|
|
44
|
+
* Requiring provenance must never reject such a host outright.
|
|
45
|
+
*
|
|
40
46
|
* Normally reported ownerless outputs carry their actual failed proof boundary
|
|
41
47
|
* in emittedSourceProofFailures. These explanations preserve compiler streams
|
|
42
48
|
* and status; they do not replace missing ownership with a successful claim.
|
|
@@ -63,6 +69,16 @@ export function runExternalEmitProvenance(options: {
|
|
|
63
69
|
/** Same child environment used by the emitting command. */
|
|
64
70
|
env: NodeJS.ProcessEnv;
|
|
65
71
|
|
|
72
|
+
/**
|
|
73
|
+
* How the emitting command's writes are known. `reported` (the default) reads
|
|
74
|
+
* the producer's own `TSFILE:` rows. `observed` serves a foreign compiler
|
|
75
|
+
* host that cannot report them, a plugin library's own executable such as
|
|
76
|
+
* typia's: `binary` and `args` then only drive the read-only probes, the
|
|
77
|
+
* callback runs that host unchanged, and a write is a predicted output whose
|
|
78
|
+
* metadata moved across the run.
|
|
79
|
+
*/
|
|
80
|
+
writes?: "reported" | "observed";
|
|
81
|
+
|
|
66
82
|
/**
|
|
67
83
|
* Execute the single emitting command, preserving raw output and whether the
|
|
68
84
|
* child exited normally rather than being killed during reporting.
|
|
@@ -79,15 +95,22 @@ export function runExternalEmitProvenance(options: {
|
|
|
79
95
|
const probe = prepareProbe(options, (reason) => {
|
|
80
96
|
refusal = reason;
|
|
81
97
|
});
|
|
98
|
+
const observed = options.writes === "observed";
|
|
99
|
+
const outputsBefore =
|
|
100
|
+
observed && probe !== undefined
|
|
101
|
+
? observePredictedOutputs(probe)
|
|
102
|
+
: undefined;
|
|
82
103
|
const { result, completedNormally } = options.run(
|
|
83
|
-
probe === undefined
|
|
104
|
+
probe === undefined || observed
|
|
84
105
|
? options.args
|
|
85
106
|
: [...options.args, "--listFiles", "true", "--listEmittedFiles", "true"],
|
|
86
107
|
);
|
|
87
108
|
const lines = result.stdout.split(/\r?\n/);
|
|
88
109
|
const sourceLines = new Set(probe?.files);
|
|
89
110
|
const stdout =
|
|
90
|
-
probe === undefined ||
|
|
111
|
+
probe === undefined ||
|
|
112
|
+
observed ||
|
|
113
|
+
probe.config.compilerOptions.listFiles === true
|
|
91
114
|
? result.stdout
|
|
92
115
|
: result.stdout
|
|
93
116
|
.split(/(?<=\n)/)
|
|
@@ -100,7 +123,12 @@ export function runExternalEmitProvenance(options: {
|
|
|
100
123
|
emittedSourceProofFailures: undefined,
|
|
101
124
|
stdout,
|
|
102
125
|
};
|
|
103
|
-
const written =
|
|
126
|
+
const written =
|
|
127
|
+
outputsBefore !== undefined
|
|
128
|
+
? movedOutputs(outputsBefore)
|
|
129
|
+
: observed
|
|
130
|
+
? []
|
|
131
|
+
: writtenJavaScript(result.stdout, options.cwd);
|
|
104
132
|
const emittedSources: Record<string, readonly string[]> = Object.create(null);
|
|
105
133
|
const emittedSourceProofFailures: Record<string, string> =
|
|
106
134
|
Object.create(null);
|
|
@@ -115,7 +143,11 @@ export function runExternalEmitProvenance(options: {
|
|
|
115
143
|
};
|
|
116
144
|
}
|
|
117
145
|
|
|
118
|
-
|
|
146
|
+
// An observed host lists nothing; it compiles the same selected tsconfig, and
|
|
147
|
+
// the source observations below still prove that selection held still.
|
|
148
|
+
const listed = observed
|
|
149
|
+
? probe.files
|
|
150
|
+
: compilerSourceList(lines, options.cwd);
|
|
119
151
|
const sameSelection = JSON.stringify(listed) === JSON.stringify(probe.files);
|
|
120
152
|
|
|
121
153
|
let stable = sameSelection;
|
|
@@ -554,6 +586,54 @@ function createOutputPrediction(
|
|
|
554
586
|
};
|
|
555
587
|
}
|
|
556
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Every output the selected sources predict, with its metadata before an
|
|
591
|
+
* observed host runs.
|
|
592
|
+
*/
|
|
593
|
+
function observePredictedOutputs(probe: {
|
|
594
|
+
base: string;
|
|
595
|
+
config: { compilerOptions: Record<string, unknown> };
|
|
596
|
+
files: readonly string[];
|
|
597
|
+
}): Map<string, string> {
|
|
598
|
+
const predict = createOutputPrediction(probe.config, probe.base);
|
|
599
|
+
const outputs = new Map<string, string>();
|
|
600
|
+
for (const source of probe.files)
|
|
601
|
+
for (const output of predict(source))
|
|
602
|
+
outputs.set(output, outputObservation(output));
|
|
603
|
+
return outputs;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* The predicted outputs an observed host wrote: present now, with metadata
|
|
608
|
+
* other than before. A rewrite inside one clock tick at the same size and inode
|
|
609
|
+
* stays invisible, which is why a host that can report its writes must declare
|
|
610
|
+
* `emitProvenance` instead.
|
|
611
|
+
*/
|
|
612
|
+
function movedOutputs(before: ReadonlyMap<string, string>): string[] {
|
|
613
|
+
const written: string[] = [];
|
|
614
|
+
for (const [output, observation] of before) {
|
|
615
|
+
const after = outputObservation(output);
|
|
616
|
+
if (after !== "missing" && after !== observation) written.push(output);
|
|
617
|
+
}
|
|
618
|
+
return written;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Device, inode, size and both clocks of one output, or `missing`. */
|
|
622
|
+
function outputObservation(file: string): string {
|
|
623
|
+
try {
|
|
624
|
+
const stats = fs.lstatSync(file, { bigint: true });
|
|
625
|
+
return [
|
|
626
|
+
stats.dev,
|
|
627
|
+
stats.ino,
|
|
628
|
+
stats.size,
|
|
629
|
+
stats.mtimeNs,
|
|
630
|
+
stats.ctimeNs,
|
|
631
|
+
].join(":");
|
|
632
|
+
} catch {
|
|
633
|
+
return "missing";
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
557
637
|
/** The writer's TSFILE report, rather than existing files, establishes writes. */
|
|
558
638
|
function writtenJavaScript(stdout: string, cwd: string): string[] {
|
|
559
639
|
const outputs = new Set<string>();
|
package/src/internal/E2ETrace.ts
CHANGED
|
@@ -62,6 +62,7 @@ export namespace E2ETrace {
|
|
|
62
62
|
owner?: { hostname: string; pid: number };
|
|
63
63
|
result: string;
|
|
64
64
|
errorCode?: string;
|
|
65
|
+
state?: string;
|
|
65
66
|
},
|
|
66
67
|
) => void)
|
|
67
68
|
| undefined {
|
|
@@ -86,6 +87,7 @@ export namespace E2ETrace {
|
|
|
86
87
|
owner?: { hostname: string; pid: number };
|
|
87
88
|
result: string;
|
|
88
89
|
errorCode?: string;
|
|
90
|
+
state?: string;
|
|
89
91
|
},
|
|
90
92
|
): void => {
|
|
91
93
|
try {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Observe whether process `pid` on this host is still running.
|
|
5
|
+
*
|
|
6
|
+
* Signal zero answers whether the kernel still holds a process with that id.
|
|
7
|
+
* Only `ESRCH` proves absence; `EPERM` and other failures remain unknown. On
|
|
8
|
+
* Linux a successful probe can still name a zombie: a process that has exited
|
|
9
|
+
* and holds its id only until its parent collects its status. A parent that
|
|
10
|
+
* never collects, such as a container's PID 1 that is not an init or a
|
|
11
|
+
* subreaper that collects only when its own command ends, keeps the zombie
|
|
12
|
+
* indefinitely, so its `/proc` status reports it absent too. A leader whose own
|
|
13
|
+
* thread exited also reads as a zombie while its other threads run, so the
|
|
14
|
+
* zombie must be the process's last thread. Its id cannot be reused while the
|
|
15
|
+
* zombie holds it, so this never names a live process absent. An absent answer
|
|
16
|
+
* carries its evidence: ESRCH, the /proc read's code when collection raced it,
|
|
17
|
+
* or the zombie's state, so a diagnostic reader can check which one held.
|
|
18
|
+
*
|
|
19
|
+
* @evidence contracts/common.md#principled-implementation ESRCH, or a Linux /proc status of zombie or dead with no other thread, proves the process exited; success with any other status is presence, and every other failure stays unknown.
|
|
20
|
+
* @evidence contracts/common.md#clear-and-simple-design One probe gives the ttsx run, dependency-lock and plugin-lock owners the same exit decision.
|
|
21
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The native signal-zero probe and the kernel's own process status decide; no PID list, timeout or error-to-absent fallback substitutes for them.
|
|
22
|
+
* @evidence contracts/common.md#meaningful-documentation The paragraph explains why a zombie answers signal zero, which parents leave one behind, why a zombie leader with running threads is not one, and why its id cannot name a live process.
|
|
23
|
+
* @evidence contracts/portability.md#os-neutral-implementation Node's signal-zero probe works on every platform; only Linux exposes zombies through /proc, and elsewhere the probe's answer stands.
|
|
24
|
+
* @evidence contracts/performance.md#efficient-algorithms One syscall, plus one small /proc read on Linux when the process exists.
|
|
25
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Process state is a current observation and is never cached as proof for a later decision.
|
|
26
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The probe retains no handle; the status text becomes reclaimable on return.
|
|
27
|
+
*/
|
|
28
|
+
export function probeLocalProcess(pid: number): {
|
|
29
|
+
result: "present" | "absent" | "unknown";
|
|
30
|
+
errorCode?: string;
|
|
31
|
+
|
|
32
|
+
/** The Linux /proc state that proved an exited process absent. */
|
|
33
|
+
state?: string;
|
|
34
|
+
} {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid, 0);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const code = (error as NodeJS.ErrnoException | null)?.code;
|
|
39
|
+
return { result: code === "ESRCH" ? "absent" : "unknown", errorCode: code };
|
|
40
|
+
}
|
|
41
|
+
if (process.platform !== "linux") return { result: "present" };
|
|
42
|
+
let status: string;
|
|
43
|
+
try {
|
|
44
|
+
status = fs.readFileSync(`/proc/${pid}/status`, "utf8");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const code = (error as NodeJS.ErrnoException | null)?.code;
|
|
47
|
+
// The process was collected between the probe and the read.
|
|
48
|
+
if (code === "ENOENT" || code === "ESRCH")
|
|
49
|
+
return { result: "absent", errorCode: code };
|
|
50
|
+
return { result: "unknown", errorCode: code };
|
|
51
|
+
}
|
|
52
|
+
// A leader whose own thread exited reads as a zombie while its other threads
|
|
53
|
+
// still run; only a zombie that is the process's last thread has exited.
|
|
54
|
+
const state = /^State:\s*(\S)/m.exec(status)?.[1];
|
|
55
|
+
const threads = /^Threads:\s*(\d+)/m.exec(status)?.[1];
|
|
56
|
+
// The state travels with an absent answer: signal zero succeeded, so it is the
|
|
57
|
+
// only evidence of the exit a diagnostic reader can check.
|
|
58
|
+
return (state === "Z" || state === "X") && threads === "1"
|
|
59
|
+
? { result: "absent", state }
|
|
60
|
+
: { result: "present" };
|
|
61
|
+
}
|
|
@@ -53,6 +53,9 @@ export namespace ProcessOwnedDirectory {
|
|
|
53
53
|
|
|
54
54
|
/** Native code already caught by the operation, if it supplied one. */
|
|
55
55
|
errorCode?: string;
|
|
56
|
+
|
|
57
|
+
/** The Linux /proc state that proved an exited owner absent. */
|
|
58
|
+
state?: string;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/**
|
|
@@ -192,12 +195,13 @@ export namespace ProcessOwnedDirectory {
|
|
|
192
195
|
owner,
|
|
193
196
|
observe === undefined
|
|
194
197
|
? undefined
|
|
195
|
-
: (result, errorCode) =>
|
|
198
|
+
: (result, errorCode, state) =>
|
|
196
199
|
report(observe, {
|
|
197
200
|
record: name,
|
|
198
201
|
owner,
|
|
199
202
|
result,
|
|
200
203
|
...(errorCode === undefined ? {} : { errorCode }),
|
|
204
|
+
...(state === undefined ? {} : { state }),
|
|
201
205
|
}),
|
|
202
206
|
)
|
|
203
207
|
)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
|
|
3
|
+
import { probeLocalProcess } from "../../../internal/probeLocalProcess";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Whether the process an owner record names has certainly exited: it ran on
|
|
5
7
|
* this host, and no process with its pid is alive.
|
|
6
8
|
*
|
|
7
9
|
* A record from another host proves nothing, because its pid names a process
|
|
8
10
|
* this machine cannot see, so it is never reported gone. An `EPERM` from the
|
|
9
|
-
* probe does not establish absence, so it counts as alive. Only `ESRCH
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* owner's state.
|
|
11
|
+
* probe does not establish absence, so it counts as alive. Only `ESRCH`, or a
|
|
12
|
+
* Linux zombie that has exited but was never collected, proves absence; other
|
|
13
|
+
* probe failures remain unknown. A recycled pid makes the answer err toward
|
|
14
|
+
* "alive": a caller that reclaims on "gone" never takes a live owner's state.
|
|
13
15
|
*
|
|
14
16
|
* Callers supply a validated positive process id, not a process-group selector.
|
|
15
17
|
* Hostname comparison is a host-label convention, not machine authentication:
|
|
@@ -18,13 +20,13 @@ import os from "node:os";
|
|
|
18
20
|
* An optional diagnostic receives this same observation without another probe;
|
|
19
21
|
* a diagnostic failure cannot change the ownership decision.
|
|
20
22
|
*
|
|
21
|
-
* @evidence contracts/common.md#principled-implementation Reclamation requires both the recorded local hostname and ESRCH
|
|
22
|
-
* @evidence contracts/common.md#clear-and-simple-design One conservative predicate supplies the same owner-liveness decision to lock recovery and directory sweeping.
|
|
23
|
-
* @evidence contracts/common.md#prohibited-implementation-shortcuts Native process existence is queried through process.kill(pid, 0); no fixture pid or broad error-to-dead fallback substitutes for ownership evidence.
|
|
24
|
-
* @evidence contracts/common.md#meaningful-documentation The native explanation distinguishes a remote host, a denied or unknown probe and pid recycling, stating why ambiguity protects a live owner's state.
|
|
23
|
+
* @evidence contracts/common.md#principled-implementation Reclamation requires both the recorded local hostname and probeLocalProcess's absence proof, ESRCH or an uncollected Linux zombie; permission, invalid-pid and unknown failures cannot establish that an owner is gone.
|
|
24
|
+
* @evidence contracts/common.md#clear-and-simple-design One conservative predicate supplies the same owner-liveness decision to lock recovery and directory sweeping, and shares its probe with plugin build locks.
|
|
25
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Native process existence is queried through process.kill(pid, 0) and the kernel's /proc state; no fixture pid or broad error-to-dead fallback substitutes for ownership evidence.
|
|
26
|
+
* @evidence contracts/common.md#meaningful-documentation The native explanation distinguishes a remote host, a denied or unknown probe, an uncollected zombie and pid recycling, stating why ambiguity protects a live owner's state.
|
|
25
27
|
* @evidence contracts/portability.md#os-neutral-implementation Node exposes the host's process-existence probe and portable error codes; hostname comparison scopes pid identity to this host without assuming POSIX signals can terminate Windows processes.
|
|
26
28
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources This observation retains no handle, historical state or cache; temporary lowercase hostname strings become reclaimable after return.
|
|
27
|
-
* @evidence contracts/performance.md#efficient-algorithms Native hostname lookup and two lowercase string constructions/comparison process host-label text before at most one native pid probe; the fixed probe count is not a fixed text or operating-system cost.
|
|
29
|
+
* @evidence contracts/performance.md#efficient-algorithms Native hostname lookup and two lowercase string constructions/comparison process host-label text before at most one native pid probe and, on Linux, one /proc state read; the fixed probe count is not a fixed text or operating-system cost.
|
|
28
30
|
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Host labels and native process existence are current observations; this predicate coordinates no cross-call cache or reusable liveness proof.
|
|
29
31
|
*/
|
|
30
32
|
export function isLocalProcessGone(
|
|
@@ -38,14 +40,16 @@ export function isLocalProcessGone(
|
|
|
38
40
|
observe?: (
|
|
39
41
|
result: "remote" | "present" | "absent" | "unknown",
|
|
40
42
|
errorCode?: string,
|
|
43
|
+
state?: string,
|
|
41
44
|
) => void,
|
|
42
45
|
): boolean {
|
|
43
46
|
const report = (
|
|
44
47
|
result: "remote" | "present" | "absent" | "unknown",
|
|
45
48
|
errorCode?: string,
|
|
49
|
+
state?: string,
|
|
46
50
|
): void => {
|
|
47
51
|
try {
|
|
48
|
-
observe?.(result, errorCode);
|
|
52
|
+
observe?.(result, errorCode, state);
|
|
49
53
|
} catch {
|
|
50
54
|
// Diagnostics own no policy.
|
|
51
55
|
}
|
|
@@ -54,14 +58,7 @@ export function isLocalProcessGone(
|
|
|
54
58
|
report("remote");
|
|
55
59
|
return false;
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
const code = (error as NodeJS.ErrnoException | null)?.code;
|
|
63
|
-
const absent = code === "ESRCH";
|
|
64
|
-
report(absent ? "absent" : "unknown", code);
|
|
65
|
-
return absent;
|
|
66
|
-
}
|
|
61
|
+
const { result, errorCode, state } = probeLocalProcess(owner.pid);
|
|
62
|
+
report(result, errorCode, state);
|
|
63
|
+
return result === "absent";
|
|
67
64
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
import type { DirectoryWatcher } from "./DirectoryWatcher";
|
|
@@ -116,6 +117,7 @@ export class FseventsStreams {
|
|
|
116
117
|
const displaced = [...(this.descendants.get(directory) ?? [])];
|
|
117
118
|
const stream: Stream = {
|
|
118
119
|
active: true,
|
|
120
|
+
openedAt: Date.now(),
|
|
119
121
|
root: directory,
|
|
120
122
|
stop: () => undefined,
|
|
121
123
|
watches: new Map(),
|
|
@@ -209,6 +211,9 @@ type Watch = {
|
|
|
209
211
|
|
|
210
212
|
type Stream = {
|
|
211
213
|
active: boolean;
|
|
214
|
+
|
|
215
|
+
/** When the stream opened; nothing born before it was created in it. */
|
|
216
|
+
openedAt: number;
|
|
212
217
|
root: string;
|
|
213
218
|
stop: () => unknown;
|
|
214
219
|
watches: Map<string, Set<Watch>>;
|
|
@@ -218,6 +223,8 @@ type Stream = {
|
|
|
218
223
|
const GAP = 0x1 | 0x2 | 0x4 | 0x8 | 0x20 | 0x40 | 0x80;
|
|
219
224
|
const MODIFIED = 0x400 | 0x1000 | 0x2000 | 0x4000 | 0x8000;
|
|
220
225
|
const RENAMED = 0x100 | 0x200 | 0x800;
|
|
226
|
+
const CREATED = 0x100;
|
|
227
|
+
const MOVED = 0x200 | 0x800;
|
|
221
228
|
|
|
222
229
|
function addWatch(stream: Stream, watch: Watch): void {
|
|
223
230
|
const watches = stream.watches.get(watch.directory) ?? new Set<Watch>();
|
|
@@ -248,6 +255,21 @@ function deliver(stream: Stream, file: string, flags: number): void {
|
|
|
248
255
|
deliverGap(stream);
|
|
249
256
|
return;
|
|
250
257
|
}
|
|
258
|
+
// FSEvents keeps ItemCreated on later writes to a path it saw created, so a
|
|
259
|
+
// plain edit of a file older than this stream arrived as a rename. An object
|
|
260
|
+
// born before the stream opened was not created in it; its other flags
|
|
261
|
+
// decide. A removal or a rename keeps the event structural.
|
|
262
|
+
if (
|
|
263
|
+
(flags & CREATED) !== 0 &&
|
|
264
|
+
(flags & MOVED) === 0 &&
|
|
265
|
+
(flags & MODIFIED) !== 0
|
|
266
|
+
) {
|
|
267
|
+
try {
|
|
268
|
+
if (fs.lstatSync(target).birthtimeMs < stream.openedAt) flags &= ~CREATED;
|
|
269
|
+
} catch {
|
|
270
|
+
// A path gone by now keeps its creation flag.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
251
273
|
const event =
|
|
252
274
|
(flags & MODIFIED) !== 0 && (flags & RENAMED) === 0 ? "change" : "rename";
|
|
253
275
|
const deliveries: Array<{ name: string; watch: Watch }> = [];
|
|
@@ -529,17 +529,16 @@ export function loadProjectPlugins(options: {
|
|
|
529
529
|
records[candidate.index]!.kind === "executable" &&
|
|
530
530
|
!proposals[candidate.index]!.ownModule,
|
|
531
531
|
);
|
|
532
|
+
const buildDirectories = pluginBuildDirectories(
|
|
533
|
+
records,
|
|
534
|
+
effectiveEnv,
|
|
535
|
+
watchReplacements,
|
|
536
|
+
packageReaders,
|
|
537
|
+
);
|
|
532
538
|
// Admission can now narrow the provisional interests without restamping the
|
|
533
539
|
// retained package/module baselines. Final host errors still keep these roots.
|
|
534
|
-
options.onWatchInputs?.(
|
|
535
|
-
|
|
536
|
-
records,
|
|
537
|
-
effectiveEnv,
|
|
538
|
-
watchReplacements,
|
|
539
|
-
packageReaders,
|
|
540
|
-
),
|
|
541
|
-
sourceIdentities,
|
|
542
|
-
);
|
|
540
|
+
options.onWatchInputs?.(buildDirectories, sourceIdentities);
|
|
541
|
+
const keyedDirectories = new Set(buildDirectories);
|
|
543
542
|
const linkedContributors = records
|
|
544
543
|
.filter((record) => record.stage === "transform")
|
|
545
544
|
.flatMap((record) =>
|
|
@@ -737,8 +736,10 @@ export function loadProjectPlugins(options: {
|
|
|
737
736
|
binary,
|
|
738
737
|
// This fallback is compiled from our utility-host, whose actual writer
|
|
739
738
|
// publishes provenance and supports a separate compiler argument cwd.
|
|
740
|
-
// A separately selected executable host must opt
|
|
741
|
-
//
|
|
739
|
+
// A separately selected executable host must opt in itself; a linked
|
|
740
|
+
// library cannot certify that foreign host. An undeclared capability
|
|
741
|
+
// only withholds the optional protocol: build execution still runs such
|
|
742
|
+
// a host and observes it from outside, never rejecting it.
|
|
742
743
|
capabilities:
|
|
743
744
|
record.stage === "transform" &&
|
|
744
745
|
record.kind === "linked" &&
|
|
@@ -781,10 +782,12 @@ export function loadProjectPlugins(options: {
|
|
|
781
782
|
? { observationsComplete: false as const }
|
|
782
783
|
: {}),
|
|
783
784
|
// The directories the watch inputs named before the builds, as the builds
|
|
784
|
-
// read them.
|
|
785
|
+
// read them. Package selection shares these readings and also reads a
|
|
786
|
+
// linked plugin's module root, which no build keys on, so only the build
|
|
787
|
+
// directories are reported.
|
|
785
788
|
pluginSources: Object.fromEntries(
|
|
786
789
|
[...sourceDigests]
|
|
787
|
-
.filter(([directory]) =>
|
|
790
|
+
.filter(([directory]) => keyedDirectories.has(directory))
|
|
788
791
|
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
789
792
|
.map(([directory, sourceDigest]) => [
|
|
790
793
|
directory,
|
|
@@ -322,6 +322,9 @@ export namespace GoBuildCacheCoordination {
|
|
|
322
322
|
// the child process required for `go build`. The fallback inherits only the
|
|
323
323
|
// low standard descriptors and checks its parent PID. An IPC channel would
|
|
324
324
|
// allocate another high descriptor and recreate Darwin's spawn EBADF limit.
|
|
325
|
+
// A dead parent that nobody collects stays a zombie and still answers
|
|
326
|
+
// signal zero, but POSIX reparents the heartbeat when its parent exits, so
|
|
327
|
+
// a changed parent id ends it as well; Windows never reparents.
|
|
325
328
|
const ready = `${file}.heartbeat-${crypto.randomBytes(16).toString("hex")}`;
|
|
326
329
|
let heartbeatChild: ReturnType<typeof spawn> | undefined;
|
|
327
330
|
try {
|
|
@@ -334,7 +337,7 @@ export namespace GoBuildCacheCoordination {
|
|
|
334
337
|
"const parent = Number(process.argv[3]);",
|
|
335
338
|
"const ready = process.argv[4];",
|
|
336
339
|
"const timer = setInterval(() => {",
|
|
337
|
-
" try { process.kill(parent, 0); } catch { clearInterval(timer); process.exit(0); }",
|
|
340
|
+
" try { if (process.ppid !== parent) throw 0; process.kill(parent, 0); } catch { clearInterval(timer); process.exit(0); }",
|
|
338
341
|
" try {",
|
|
339
342
|
' if (JSON.parse(fs.readFileSync(file, "utf8")).status === "complete") {',
|
|
340
343
|
" clearInterval(timer); process.exit(0);",
|