@mcmcjs/julia 0.4.0 → 0.6.0
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/dist/driver.jl +10 -214
- package/dist/fitlib.jl +289 -0
- package/dist/index.cjs +403 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -16
- package/dist/index.d.ts +82 -16
- package/dist/index.js +399 -59
- package/dist/index.js.map +1 -1
- package/dist/worker.jl +95 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ import { createRunner } from "@mcmcjs/engine";
|
|
|
5
5
|
import { createRunner as createRunner2 } from "@mcmcjs/engine";
|
|
6
6
|
var defaultRunner = createRunner();
|
|
7
7
|
function candidates(binary) {
|
|
8
|
-
|
|
8
|
+
const home = process.env.HOME || homedir();
|
|
9
|
+
return [join(home, ".juliaup", "bin", binary), binary];
|
|
9
10
|
}
|
|
10
11
|
async function detect(binary, parseVersion, runner) {
|
|
11
12
|
for (const path of candidates(binary)) {
|
|
@@ -52,8 +53,7 @@ var juliaEngine = {
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
// src/fit.ts
|
|
55
|
-
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
56
|
-
import { tmpdir } from "os";
|
|
56
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
57
57
|
import { join as join3 } from "path";
|
|
58
58
|
import {
|
|
59
59
|
canonicalJson,
|
|
@@ -63,11 +63,19 @@ import {
|
|
|
63
63
|
|
|
64
64
|
// src/runner-common.ts
|
|
65
65
|
import { createHash } from "crypto";
|
|
66
|
+
import { tmpdir } from "os";
|
|
66
67
|
import { dirname, join as join2 } from "path";
|
|
67
68
|
import { fileURLToPath } from "url";
|
|
69
|
+
function sharedTmpParent() {
|
|
70
|
+
const uid = typeof process.getuid === "function" ? `-${process.getuid()}` : "";
|
|
71
|
+
return join2(tmpdir(), `mcmcjs${uid}`);
|
|
72
|
+
}
|
|
68
73
|
function driverPath() {
|
|
69
74
|
return join2(dirname(fileURLToPath(import.meta.url)), "driver.jl");
|
|
70
75
|
}
|
|
76
|
+
function workerPath() {
|
|
77
|
+
return join2(dirname(fileURLToPath(import.meta.url)), "worker.jl");
|
|
78
|
+
}
|
|
71
79
|
function sha256(text) {
|
|
72
80
|
return createHash("sha256").update(text).digest("hex");
|
|
73
81
|
}
|
|
@@ -89,45 +97,40 @@ var STAGES = /* @__PURE__ */ new Set([
|
|
|
89
97
|
"load_samples",
|
|
90
98
|
"predict",
|
|
91
99
|
"write",
|
|
92
|
-
"spawn"
|
|
100
|
+
"spawn",
|
|
101
|
+
"worker"
|
|
93
102
|
]);
|
|
94
103
|
function toStage(value) {
|
|
95
104
|
return typeof value === "string" && STAGES.has(value) ? value : void 0;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
// src/fit.ts
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
108
|
+
function makeRequestDir(kind) {
|
|
109
|
+
const parent = sharedTmpParent();
|
|
110
|
+
mkdirSync(parent, { recursive: true });
|
|
111
|
+
return mkdtempSync(join3(parent, `${kind}-`));
|
|
112
|
+
}
|
|
113
|
+
function fitRequest(spec, outPath) {
|
|
114
|
+
return {
|
|
115
|
+
schema_version: spec.schema_version,
|
|
116
|
+
backend: { id: spec.backend.id },
|
|
117
|
+
model: { file: spec.modelPath, entry: spec.model.entry },
|
|
118
|
+
data: spec.data,
|
|
119
|
+
sampler: spec.sampler,
|
|
120
|
+
seed: spec.seed,
|
|
121
|
+
out: outPath
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
|
|
102
125
|
const runtimeRequested = spec.backend.version;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
data: spec.data,
|
|
112
|
-
sampler: spec.sampler,
|
|
113
|
-
seed: spec.seed,
|
|
114
|
-
out: io.outPath
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
const args = [
|
|
118
|
-
...resolved.args,
|
|
119
|
-
"--startup-file=no",
|
|
120
|
-
`--project=${io.projectDir}`,
|
|
121
|
-
driverPath(),
|
|
122
|
-
requestPath
|
|
123
|
-
];
|
|
124
|
-
const { stdout, stderr, code } = await io.spawn(resolved.command, args);
|
|
125
|
-
const elapsedMs = Math.round(performance.now() - start);
|
|
126
|
-
if (code !== 0 || !existsSync(io.outPath)) {
|
|
127
|
-
const failure = lastJsonLine(stderr);
|
|
128
|
-
const stage = toStage(failure?.stage);
|
|
129
|
-
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
130
|
-
return { status: "error", runtimeRequested, elapsedMs, stage, error };
|
|
126
|
+
if (!existsSync(io.outPath)) {
|
|
127
|
+
return {
|
|
128
|
+
status: "error",
|
|
129
|
+
runtimeRequested,
|
|
130
|
+
elapsedMs,
|
|
131
|
+
stage: "write",
|
|
132
|
+
error: "the runtime reported success but wrote no samples file"
|
|
133
|
+
};
|
|
131
134
|
}
|
|
132
135
|
try {
|
|
133
136
|
parseSamples(readFileSync(io.outPath, "utf8"));
|
|
@@ -140,7 +143,6 @@ async function runFit(spec, resolved, io) {
|
|
|
140
143
|
error: `samples file did not parse: ${error.message}`
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
|
-
const provenance = lastJsonLine(stdout);
|
|
144
146
|
const julia = provenance?.julia_version;
|
|
145
147
|
const record = {
|
|
146
148
|
schema_version: RUN_RECORD_SCHEMA_VERSION,
|
|
@@ -151,12 +153,13 @@ async function runFit(spec, resolved, io) {
|
|
|
151
153
|
manifest_sha256: provenance?.manifest_sha256,
|
|
152
154
|
packages: provenance?.packages,
|
|
153
155
|
model_sha256: existsSync(spec.modelPath) ? sha256(readFileSync(spec.modelPath, "utf8")) : void 0,
|
|
154
|
-
data_sha256: sha256(canonicalJson(spec.data)),
|
|
156
|
+
data_sha256: io.dataSha256 ?? sha256(canonicalJson(spec.data)),
|
|
157
|
+
...io.dataFile ? { data_file: io.dataFile } : {},
|
|
155
158
|
samples_file: io.outPath,
|
|
156
159
|
started_at: startedAt,
|
|
157
160
|
elapsed_ms: elapsedMs
|
|
158
161
|
};
|
|
159
|
-
writeFileSync(`${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
162
|
+
writeFileSync(io.recordPath ?? `${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
160
163
|
`);
|
|
161
164
|
return {
|
|
162
165
|
status: "ok",
|
|
@@ -166,10 +169,43 @@ async function runFit(spec, resolved, io) {
|
|
|
166
169
|
elapsedMs
|
|
167
170
|
};
|
|
168
171
|
}
|
|
172
|
+
async function runFit(spec, resolved, io) {
|
|
173
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
174
|
+
const start = performance.now();
|
|
175
|
+
const runtimeRequested = spec.backend.version;
|
|
176
|
+
const ownTmp = io.tmpDir === void 0;
|
|
177
|
+
const tmp = io.tmpDir ?? makeRequestDir("fit");
|
|
178
|
+
const requestPath = join3(tmp, "request.json");
|
|
179
|
+
writeFileSync(requestPath, JSON.stringify(fitRequest(spec, io.outPath)));
|
|
180
|
+
const args = [
|
|
181
|
+
...resolved.args,
|
|
182
|
+
"--startup-file=no",
|
|
183
|
+
`--project=${io.projectDir}`,
|
|
184
|
+
driverPath(),
|
|
185
|
+
requestPath
|
|
186
|
+
];
|
|
187
|
+
let stdout;
|
|
188
|
+
let stderr;
|
|
189
|
+
let code;
|
|
190
|
+
try {
|
|
191
|
+
({ stdout, stderr, code } = await io.spawn(resolved.command, args, {
|
|
192
|
+
onProgress: io.onProgress
|
|
193
|
+
}));
|
|
194
|
+
} finally {
|
|
195
|
+
if (ownTmp) rmSync(tmp, { recursive: true, force: true });
|
|
196
|
+
}
|
|
197
|
+
const elapsedMs = Math.round(performance.now() - start);
|
|
198
|
+
if (code !== 0 || !existsSync(io.outPath)) {
|
|
199
|
+
const failure = lastJsonLine(stderr);
|
|
200
|
+
const stage = toStage(failure?.stage);
|
|
201
|
+
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
202
|
+
return { status: "error", runtimeRequested, elapsedMs, stage, error };
|
|
203
|
+
}
|
|
204
|
+
return finalizeOkFit(spec, resolved, io, lastJsonLine(stdout), startedAt, elapsedMs);
|
|
205
|
+
}
|
|
169
206
|
|
|
170
207
|
// src/predict.ts
|
|
171
|
-
import { existsSync as existsSync2,
|
|
172
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
208
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
173
209
|
import { join as join4 } from "path";
|
|
174
210
|
import {
|
|
175
211
|
canonicalJson as canonicalJson2,
|
|
@@ -191,7 +227,8 @@ async function runPredict(spec, resolved, io) {
|
|
|
191
227
|
const start = performance.now();
|
|
192
228
|
const runtimeRequested = spec.backend.version;
|
|
193
229
|
const data = predictData(spec);
|
|
194
|
-
const
|
|
230
|
+
const ownTmp = io.tmpDir === void 0;
|
|
231
|
+
const tmp = io.tmpDir ?? makeRequestDir("predict");
|
|
195
232
|
const requestPath = join4(tmp, "request.json");
|
|
196
233
|
writeFileSync2(
|
|
197
234
|
requestPath,
|
|
@@ -214,7 +251,14 @@ async function runPredict(spec, resolved, io) {
|
|
|
214
251
|
driverPath(),
|
|
215
252
|
requestPath
|
|
216
253
|
];
|
|
217
|
-
|
|
254
|
+
let stdout;
|
|
255
|
+
let stderr;
|
|
256
|
+
let code;
|
|
257
|
+
try {
|
|
258
|
+
({ stdout, stderr, code } = await io.spawn(resolved.command, args));
|
|
259
|
+
} finally {
|
|
260
|
+
if (ownTmp) rmSync2(tmp, { recursive: true, force: true });
|
|
261
|
+
}
|
|
218
262
|
const elapsedMs = Math.round(performance.now() - start);
|
|
219
263
|
if (code !== 0 || !existsSync2(io.outPath)) {
|
|
220
264
|
const failure = lastJsonLine(stderr);
|
|
@@ -262,9 +306,10 @@ async function runPredict(spec, resolved, io) {
|
|
|
262
306
|
}
|
|
263
307
|
|
|
264
308
|
// src/project.ts
|
|
265
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
309
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
266
310
|
import { homedir as homedir2 } from "os";
|
|
267
311
|
import { join as join5 } from "path";
|
|
312
|
+
import { canonicalJson as canonicalJson3 } from "@mcmcjs/core";
|
|
268
313
|
import { createRunner as createRunner3 } from "@mcmcjs/engine";
|
|
269
314
|
var PACKAGES = [
|
|
270
315
|
"Turing",
|
|
@@ -277,29 +322,65 @@ var PACKAGES = [
|
|
|
277
322
|
"JSON",
|
|
278
323
|
"StableRNGs"
|
|
279
324
|
];
|
|
280
|
-
|
|
325
|
+
var VERSION_RE = /^[0-9A-Za-z.+~^=<>,\s-]+$/;
|
|
326
|
+
function validateVersionString(name, version) {
|
|
327
|
+
if (!VERSION_RE.test(version)) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`invalid version "${version}" for package ${name}; a version may contain only digits, letters, and . + ~ ^ = < > , - and spaces`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function validatePins(pins) {
|
|
334
|
+
for (const [name, version] of Object.entries(pins ?? {})) {
|
|
335
|
+
if (!PACKAGES.includes(name)) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`cannot pin unknown package "${name}"; managed packages are: ${PACKAGES.join(", ")}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
validateVersionString(name, version);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function hasPins(pins) {
|
|
344
|
+
return pins !== void 0 && Object.keys(pins).length > 0;
|
|
345
|
+
}
|
|
346
|
+
function managedProjectDir(version, pins) {
|
|
281
347
|
const base = process.env.XDG_DATA_HOME || join5(homedir2(), ".local", "share");
|
|
282
|
-
|
|
348
|
+
const root = join5(base, "mcmcjs", "julia", "env");
|
|
349
|
+
if (!version && !hasPins(pins)) return root;
|
|
350
|
+
let leaf = (version ?? "default").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
351
|
+
if (hasPins(pins)) leaf += `-${sha256(canonicalJson3(pins)).slice(0, 8)}`;
|
|
352
|
+
return join5(root, leaf);
|
|
283
353
|
}
|
|
284
354
|
function sentinelPath(dir) {
|
|
285
355
|
return join5(dir, ".mcmcjs-packages.json");
|
|
286
356
|
}
|
|
287
|
-
function
|
|
357
|
+
function expectedSentinel(pins) {
|
|
358
|
+
return canonicalJson3({ packages: [...PACKAGES].sort(), pins: pins ?? {} });
|
|
359
|
+
}
|
|
360
|
+
function provisioned(dir, pins) {
|
|
288
361
|
try {
|
|
289
|
-
return readFileSync3(sentinelPath(dir), "utf8").trim() ===
|
|
362
|
+
return readFileSync3(sentinelPath(dir), "utf8").trim() === expectedSentinel(pins);
|
|
290
363
|
} catch {
|
|
291
364
|
return false;
|
|
292
365
|
}
|
|
293
366
|
}
|
|
294
|
-
function managedProjectReady(dir = managedProjectDir()) {
|
|
295
|
-
return existsSync3(join5(dir, "Project.toml")) && provisioned(dir);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
367
|
+
function managedProjectReady(dir = managedProjectDir(), pins) {
|
|
368
|
+
return existsSync3(join5(dir, "Project.toml")) && provisioned(dir, pins);
|
|
369
|
+
}
|
|
370
|
+
function packageSpecsCode(pins) {
|
|
371
|
+
const specs = PACKAGES.map((name) => {
|
|
372
|
+
const version = pins?.[name];
|
|
373
|
+
return version ? `Pkg.PackageSpec(name=${JSON.stringify(name)}, version=${JSON.stringify(version)})` : `Pkg.PackageSpec(name=${JSON.stringify(name)})`;
|
|
374
|
+
});
|
|
375
|
+
return `[${specs.join(", ")}]`;
|
|
376
|
+
}
|
|
377
|
+
async function ensureProject(juliaBin, run = createRunner3(30 * 6e4), dir = managedProjectDir(), pins) {
|
|
378
|
+
validatePins(pins);
|
|
379
|
+
if (managedProjectReady(dir, pins)) return dir;
|
|
380
|
+
mkdirSync2(dir, { recursive: true });
|
|
381
|
+
const code = `using Pkg; Pkg.add(${packageSpecsCode(pins)}); Pkg.precompile()`;
|
|
301
382
|
await run(juliaBin, ["--startup-file=no", `--project=${dir}`, "-e", code]);
|
|
302
|
-
writeFileSync3(sentinelPath(dir),
|
|
383
|
+
writeFileSync3(sentinelPath(dir), expectedSentinel(pins));
|
|
303
384
|
return dir;
|
|
304
385
|
}
|
|
305
386
|
|
|
@@ -311,11 +392,14 @@ async function runMatrix(spec, versions, io) {
|
|
|
311
392
|
let entry;
|
|
312
393
|
try {
|
|
313
394
|
const resolved = await io.resolve(version);
|
|
395
|
+
const projectDir = await io.ensure(resolved);
|
|
314
396
|
const versioned = { ...spec, backend: { ...spec.backend, version } };
|
|
315
397
|
const result = await runFit(versioned, resolved, {
|
|
316
398
|
spawn: io.spawn,
|
|
317
|
-
projectDir
|
|
318
|
-
outPath: join6(io.outDir, `${version}.samples.json`)
|
|
399
|
+
projectDir,
|
|
400
|
+
outPath: join6(io.outDir, `${version}.samples.json`),
|
|
401
|
+
dataFile: io.dataFile,
|
|
402
|
+
dataSha256: io.dataSha256
|
|
319
403
|
});
|
|
320
404
|
entry = {
|
|
321
405
|
version,
|
|
@@ -464,7 +548,7 @@ async function resolveVersion(bin, channel, run) {
|
|
|
464
548
|
`Julia version "${channel}" is not installed. Add it with: mcmc julia version add ${channel}`
|
|
465
549
|
);
|
|
466
550
|
}
|
|
467
|
-
return { command: match.File, args: match.Args ?? [] };
|
|
551
|
+
return { command: match.File, args: match.Args ?? [], version: match.Version };
|
|
468
552
|
}
|
|
469
553
|
function assertVersionsInstalled(channels, installed) {
|
|
470
554
|
const have = new Set(installed.map((v) => v.id));
|
|
@@ -476,6 +560,256 @@ function assertVersionsInstalled(channels, installed) {
|
|
|
476
560
|
);
|
|
477
561
|
}
|
|
478
562
|
}
|
|
563
|
+
|
|
564
|
+
// src/worker.ts
|
|
565
|
+
import { spawn } from "child_process";
|
|
566
|
+
import { mkdirSync as mkdirSync3, readdirSync, rmdirSync, rmSync as rmSync3, statSync } from "fs";
|
|
567
|
+
import { createConnection } from "net";
|
|
568
|
+
import { join as join7 } from "path";
|
|
569
|
+
import { parseProgressLine } from "@mcmcjs/engine";
|
|
570
|
+
var START_WAIT_MS = 18e4;
|
|
571
|
+
var CONNECT_TIMEOUT_MS = 1e3;
|
|
572
|
+
var STOP_ACK_TIMEOUT_MS = 3e3;
|
|
573
|
+
function fitInactivityTimeoutMs() {
|
|
574
|
+
const fromEnv = Number(process.env.MCMC_WORKER_TIMEOUT_MS);
|
|
575
|
+
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 30 * 6e4;
|
|
576
|
+
}
|
|
577
|
+
var WorkerTimeoutError = class extends Error {
|
|
578
|
+
};
|
|
579
|
+
function workersDir() {
|
|
580
|
+
const runtime = process.env.XDG_RUNTIME_DIR;
|
|
581
|
+
const dir = runtime ? join7(runtime, "mcmcjs") : join7(sharedTmpParent(), "workers");
|
|
582
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
583
|
+
return dir;
|
|
584
|
+
}
|
|
585
|
+
function workerSocketPath(command, projectDir) {
|
|
586
|
+
return join7(workersDir(), `worker-${sha256(`${command}
|
|
587
|
+
${projectDir}`).slice(0, 12)}.sock`);
|
|
588
|
+
}
|
|
589
|
+
function connectOnce(path) {
|
|
590
|
+
return new Promise((resolve, reject) => {
|
|
591
|
+
const sock = createConnection(path);
|
|
592
|
+
const timer = setTimeout(() => {
|
|
593
|
+
sock.destroy();
|
|
594
|
+
reject(new Error("connect timeout"));
|
|
595
|
+
}, CONNECT_TIMEOUT_MS);
|
|
596
|
+
sock.once("connect", () => {
|
|
597
|
+
clearTimeout(timer);
|
|
598
|
+
resolve(sock);
|
|
599
|
+
});
|
|
600
|
+
sock.once("error", (error) => {
|
|
601
|
+
clearTimeout(timer);
|
|
602
|
+
reject(error);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
607
|
+
function acquireStartLock(socketPath) {
|
|
608
|
+
const lock = `${socketPath}.start`;
|
|
609
|
+
try {
|
|
610
|
+
mkdirSync3(lock);
|
|
611
|
+
return true;
|
|
612
|
+
} catch {
|
|
613
|
+
try {
|
|
614
|
+
if (Date.now() - statSync(lock).mtimeMs > START_WAIT_MS) {
|
|
615
|
+
rmdirSync(lock);
|
|
616
|
+
mkdirSync3(lock);
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function releaseStartLock(socketPath) {
|
|
625
|
+
try {
|
|
626
|
+
rmdirSync(`${socketPath}.start`);
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function ensureWorker(resolved, projectDir, notify) {
|
|
631
|
+
const path = workerSocketPath(resolved.command, projectDir);
|
|
632
|
+
try {
|
|
633
|
+
return await connectOnce(path);
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
let spawnFailure;
|
|
637
|
+
const startedHere = acquireStartLock(path);
|
|
638
|
+
if (startedHere) {
|
|
639
|
+
try {
|
|
640
|
+
rmSync3(path, { force: true });
|
|
641
|
+
notify?.("starting the Julia worker (first use loads Turing, can take a minute)...");
|
|
642
|
+
const child = spawn(
|
|
643
|
+
resolved.command,
|
|
644
|
+
[...resolved.args, "--startup-file=no", `--project=${projectDir}`, workerPath(), path],
|
|
645
|
+
{ detached: true, stdio: "ignore" }
|
|
646
|
+
);
|
|
647
|
+
child.on("error", (error) => {
|
|
648
|
+
spawnFailure = error;
|
|
649
|
+
});
|
|
650
|
+
child.on("exit", (code) => {
|
|
651
|
+
if (code !== 0 && code !== null) {
|
|
652
|
+
spawnFailure = new Error(`the worker exited with code ${code} before serving`);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
child.unref();
|
|
656
|
+
} catch (error) {
|
|
657
|
+
releaseStartLock(path);
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const deadline = Date.now() + START_WAIT_MS;
|
|
663
|
+
for (; ; ) {
|
|
664
|
+
if (spawnFailure) throw spawnFailure;
|
|
665
|
+
try {
|
|
666
|
+
return await connectOnce(path);
|
|
667
|
+
} catch {
|
|
668
|
+
}
|
|
669
|
+
if (Date.now() > deadline) throw new Error("the Julia worker did not start in time");
|
|
670
|
+
await delay(250);
|
|
671
|
+
}
|
|
672
|
+
} finally {
|
|
673
|
+
if (startedHere) releaseStartLock(path);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeoutMs()) {
|
|
677
|
+
return new Promise((resolve, reject) => {
|
|
678
|
+
let pending = "";
|
|
679
|
+
let settled = false;
|
|
680
|
+
const settle = (fn) => {
|
|
681
|
+
if (settled) return;
|
|
682
|
+
settled = true;
|
|
683
|
+
sock.setTimeout(0);
|
|
684
|
+
fn();
|
|
685
|
+
};
|
|
686
|
+
sock.setTimeout(inactivityMs, () => {
|
|
687
|
+
const error = new WorkerTimeoutError(
|
|
688
|
+
`the worker sent nothing for ${Math.round(inactivityMs / 6e4)} minutes`
|
|
689
|
+
);
|
|
690
|
+
settle(() => {
|
|
691
|
+
sock.destroy();
|
|
692
|
+
reject(error);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
sock.on("data", (chunk) => {
|
|
696
|
+
pending += chunk.toString("utf8");
|
|
697
|
+
for (; ; ) {
|
|
698
|
+
const at = pending.indexOf("\n");
|
|
699
|
+
if (at === -1) break;
|
|
700
|
+
const line = pending.slice(0, at).trim();
|
|
701
|
+
pending = pending.slice(at + 1);
|
|
702
|
+
if (!line) continue;
|
|
703
|
+
const progress = parseProgressLine(line);
|
|
704
|
+
if (progress) {
|
|
705
|
+
onProgress?.(progress);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
const doc = JSON.parse(line);
|
|
710
|
+
settle(() => {
|
|
711
|
+
sock.end();
|
|
712
|
+
resolve(doc);
|
|
713
|
+
});
|
|
714
|
+
return;
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
sock.on("error", (error) => settle(() => reject(error)));
|
|
720
|
+
sock.on("close", () => settle(() => reject(new Error("worker closed the connection"))));
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
async function runFitViaWorker(spec, resolved, io) {
|
|
724
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
725
|
+
const start = performance.now();
|
|
726
|
+
const sock = await ensureWorker(resolved, io.projectDir, io.notify);
|
|
727
|
+
sock.write(`${JSON.stringify(fitRequest(spec, io.outPath))}
|
|
728
|
+
`);
|
|
729
|
+
const final = await readWorkerResponse(sock, io.onProgress);
|
|
730
|
+
const elapsedMs = Math.round(performance.now() - start);
|
|
731
|
+
if (final.ok !== true) {
|
|
732
|
+
if (final.stage === "worker") throw new Error(String(final.error ?? "worker error"));
|
|
733
|
+
return {
|
|
734
|
+
status: "error",
|
|
735
|
+
runtimeRequested: spec.backend.version,
|
|
736
|
+
elapsedMs,
|
|
737
|
+
stage: toStage(final.stage),
|
|
738
|
+
error: String(final.error ?? "worker error")
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
return finalizeOkFit(
|
|
742
|
+
spec,
|
|
743
|
+
resolved,
|
|
744
|
+
io,
|
|
745
|
+
final.provenance,
|
|
746
|
+
startedAt,
|
|
747
|
+
elapsedMs
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
async function runFitAuto(spec, resolved, io) {
|
|
751
|
+
if (io.daemon && process.platform !== "win32") {
|
|
752
|
+
const start = performance.now();
|
|
753
|
+
try {
|
|
754
|
+
return await runFitViaWorker(spec, resolved, io);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
if (error instanceof WorkerTimeoutError) {
|
|
757
|
+
return {
|
|
758
|
+
status: "error",
|
|
759
|
+
runtimeRequested: spec.backend.version,
|
|
760
|
+
elapsedMs: Math.round(performance.now() - start),
|
|
761
|
+
stage: "worker",
|
|
762
|
+
error: `${error.message}; the worker may be wedged, try mcmc daemon stop`
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
io.notify?.(
|
|
766
|
+
`worker unavailable (${error.message}); falling back to a one-shot run`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return runFit(spec, resolved, io);
|
|
771
|
+
}
|
|
772
|
+
async function listWorkers() {
|
|
773
|
+
const dir = workersDir();
|
|
774
|
+
const sockets = readdirSync(dir).filter((f) => f.startsWith("worker-") && f.endsWith(".sock"));
|
|
775
|
+
const statuses = [];
|
|
776
|
+
for (const name of sockets) {
|
|
777
|
+
const socket = join7(dir, name);
|
|
778
|
+
let alive = false;
|
|
779
|
+
try {
|
|
780
|
+
const sock = await connectOnce(socket);
|
|
781
|
+
sock.destroy();
|
|
782
|
+
alive = true;
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
statuses.push({ socket, alive });
|
|
786
|
+
}
|
|
787
|
+
return statuses;
|
|
788
|
+
}
|
|
789
|
+
async function stopWorker(socket) {
|
|
790
|
+
let sock;
|
|
791
|
+
try {
|
|
792
|
+
sock = await connectOnce(socket);
|
|
793
|
+
} catch {
|
|
794
|
+
rmSync3(socket, { force: true });
|
|
795
|
+
return "stale";
|
|
796
|
+
}
|
|
797
|
+
const acked = await new Promise((resolve) => {
|
|
798
|
+
const timer = setTimeout(() => resolve(false), STOP_ACK_TIMEOUT_MS);
|
|
799
|
+
const finish = (ok) => {
|
|
800
|
+
clearTimeout(timer);
|
|
801
|
+
resolve(ok);
|
|
802
|
+
};
|
|
803
|
+
sock.resume();
|
|
804
|
+
sock.on("close", () => finish(true));
|
|
805
|
+
sock.on("error", () => finish(false));
|
|
806
|
+
sock.write('{"mcmcjs":"stop"}\n');
|
|
807
|
+
});
|
|
808
|
+
sock.destroy();
|
|
809
|
+
if (!acked) return "pending";
|
|
810
|
+
rmSync3(socket, { force: true });
|
|
811
|
+
return "stopped";
|
|
812
|
+
}
|
|
479
813
|
export {
|
|
480
814
|
addVersion,
|
|
481
815
|
assertVersionsInstalled,
|
|
@@ -486,6 +820,7 @@ export {
|
|
|
486
820
|
juliaEngine,
|
|
487
821
|
juliaupInstallCommand,
|
|
488
822
|
listVersions,
|
|
823
|
+
listWorkers,
|
|
489
824
|
managedProjectDir,
|
|
490
825
|
managedProjectReady,
|
|
491
826
|
planSetup,
|
|
@@ -494,10 +829,15 @@ export {
|
|
|
494
829
|
resolveVersion,
|
|
495
830
|
runDoctor,
|
|
496
831
|
runFit,
|
|
832
|
+
runFitAuto,
|
|
497
833
|
runMatrix,
|
|
498
834
|
runPredict,
|
|
499
835
|
runSetup,
|
|
500
836
|
setDefaultVersion,
|
|
501
|
-
|
|
837
|
+
sharedTmpParent,
|
|
838
|
+
stopWorker,
|
|
839
|
+
updateVersion,
|
|
840
|
+
validatePins,
|
|
841
|
+
validateVersionString
|
|
502
842
|
};
|
|
503
843
|
//# sourceMappingURL=index.js.map
|