@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.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
juliaEngine: () => juliaEngine,
|
|
30
30
|
juliaupInstallCommand: () => juliaupInstallCommand,
|
|
31
31
|
listVersions: () => listVersions,
|
|
32
|
+
listWorkers: () => listWorkers,
|
|
32
33
|
managedProjectDir: () => managedProjectDir,
|
|
33
34
|
managedProjectReady: () => managedProjectReady,
|
|
34
35
|
planSetup: () => planSetup,
|
|
@@ -37,11 +38,16 @@ __export(index_exports, {
|
|
|
37
38
|
resolveVersion: () => resolveVersion,
|
|
38
39
|
runDoctor: () => runDoctor,
|
|
39
40
|
runFit: () => runFit,
|
|
41
|
+
runFitAuto: () => runFitAuto,
|
|
40
42
|
runMatrix: () => runMatrix,
|
|
41
43
|
runPredict: () => runPredict,
|
|
42
44
|
runSetup: () => runSetup,
|
|
43
45
|
setDefaultVersion: () => setDefaultVersion,
|
|
44
|
-
|
|
46
|
+
sharedTmpParent: () => sharedTmpParent,
|
|
47
|
+
stopWorker: () => stopWorker,
|
|
48
|
+
updateVersion: () => updateVersion,
|
|
49
|
+
validatePins: () => validatePins,
|
|
50
|
+
validateVersionString: () => validateVersionString
|
|
45
51
|
});
|
|
46
52
|
module.exports = __toCommonJS(index_exports);
|
|
47
53
|
|
|
@@ -56,7 +62,8 @@ var import_engine = require("@mcmcjs/engine");
|
|
|
56
62
|
var import_engine2 = require("@mcmcjs/engine");
|
|
57
63
|
var defaultRunner = (0, import_engine.createRunner)();
|
|
58
64
|
function candidates(binary) {
|
|
59
|
-
|
|
65
|
+
const home = process.env.HOME || (0, import_node_os.homedir)();
|
|
66
|
+
return [(0, import_node_path.join)(home, ".juliaup", "bin", binary), binary];
|
|
60
67
|
}
|
|
61
68
|
async function detect(binary, parseVersion, runner) {
|
|
62
69
|
for (const path of candidates(binary)) {
|
|
@@ -104,17 +111,24 @@ var juliaEngine = {
|
|
|
104
111
|
|
|
105
112
|
// src/fit.ts
|
|
106
113
|
var import_node_fs = require("fs");
|
|
107
|
-
var import_node_os2 = require("os");
|
|
108
114
|
var import_node_path3 = require("path");
|
|
109
115
|
var import_core = require("@mcmcjs/core");
|
|
110
116
|
|
|
111
117
|
// src/runner-common.ts
|
|
112
118
|
var import_node_crypto = require("crypto");
|
|
119
|
+
var import_node_os2 = require("os");
|
|
113
120
|
var import_node_path2 = require("path");
|
|
114
121
|
var import_node_url = require("url");
|
|
122
|
+
function sharedTmpParent() {
|
|
123
|
+
const uid = typeof process.getuid === "function" ? `-${process.getuid()}` : "";
|
|
124
|
+
return (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `mcmcjs${uid}`);
|
|
125
|
+
}
|
|
115
126
|
function driverPath() {
|
|
116
127
|
return (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "driver.jl");
|
|
117
128
|
}
|
|
129
|
+
function workerPath() {
|
|
130
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "worker.jl");
|
|
131
|
+
}
|
|
118
132
|
function sha256(text) {
|
|
119
133
|
return (0, import_node_crypto.createHash)("sha256").update(text).digest("hex");
|
|
120
134
|
}
|
|
@@ -136,45 +150,40 @@ var STAGES = /* @__PURE__ */ new Set([
|
|
|
136
150
|
"load_samples",
|
|
137
151
|
"predict",
|
|
138
152
|
"write",
|
|
139
|
-
"spawn"
|
|
153
|
+
"spawn",
|
|
154
|
+
"worker"
|
|
140
155
|
]);
|
|
141
156
|
function toStage(value) {
|
|
142
157
|
return typeof value === "string" && STAGES.has(value) ? value : void 0;
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
// src/fit.ts
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
161
|
+
function makeRequestDir(kind) {
|
|
162
|
+
const parent = sharedTmpParent();
|
|
163
|
+
(0, import_node_fs.mkdirSync)(parent, { recursive: true });
|
|
164
|
+
return (0, import_node_fs.mkdtempSync)((0, import_node_path3.join)(parent, `${kind}-`));
|
|
165
|
+
}
|
|
166
|
+
function fitRequest(spec, outPath) {
|
|
167
|
+
return {
|
|
168
|
+
schema_version: spec.schema_version,
|
|
169
|
+
backend: { id: spec.backend.id },
|
|
170
|
+
model: { file: spec.modelPath, entry: spec.model.entry },
|
|
171
|
+
data: spec.data,
|
|
172
|
+
sampler: spec.sampler,
|
|
173
|
+
seed: spec.seed,
|
|
174
|
+
out: outPath
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
|
|
149
178
|
const runtimeRequested = spec.backend.version;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
data: spec.data,
|
|
159
|
-
sampler: spec.sampler,
|
|
160
|
-
seed: spec.seed,
|
|
161
|
-
out: io.outPath
|
|
162
|
-
})
|
|
163
|
-
);
|
|
164
|
-
const args = [
|
|
165
|
-
...resolved.args,
|
|
166
|
-
"--startup-file=no",
|
|
167
|
-
`--project=${io.projectDir}`,
|
|
168
|
-
driverPath(),
|
|
169
|
-
requestPath
|
|
170
|
-
];
|
|
171
|
-
const { stdout, stderr, code } = await io.spawn(resolved.command, args);
|
|
172
|
-
const elapsedMs = Math.round(performance.now() - start);
|
|
173
|
-
if (code !== 0 || !(0, import_node_fs.existsSync)(io.outPath)) {
|
|
174
|
-
const failure = lastJsonLine(stderr);
|
|
175
|
-
const stage = toStage(failure?.stage);
|
|
176
|
-
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
177
|
-
return { status: "error", runtimeRequested, elapsedMs, stage, error };
|
|
179
|
+
if (!(0, import_node_fs.existsSync)(io.outPath)) {
|
|
180
|
+
return {
|
|
181
|
+
status: "error",
|
|
182
|
+
runtimeRequested,
|
|
183
|
+
elapsedMs,
|
|
184
|
+
stage: "write",
|
|
185
|
+
error: "the runtime reported success but wrote no samples file"
|
|
186
|
+
};
|
|
178
187
|
}
|
|
179
188
|
try {
|
|
180
189
|
(0, import_core.parseSamples)((0, import_node_fs.readFileSync)(io.outPath, "utf8"));
|
|
@@ -187,7 +196,6 @@ async function runFit(spec, resolved, io) {
|
|
|
187
196
|
error: `samples file did not parse: ${error.message}`
|
|
188
197
|
};
|
|
189
198
|
}
|
|
190
|
-
const provenance = lastJsonLine(stdout);
|
|
191
199
|
const julia = provenance?.julia_version;
|
|
192
200
|
const record = {
|
|
193
201
|
schema_version: import_core.RUN_RECORD_SCHEMA_VERSION,
|
|
@@ -198,12 +206,13 @@ async function runFit(spec, resolved, io) {
|
|
|
198
206
|
manifest_sha256: provenance?.manifest_sha256,
|
|
199
207
|
packages: provenance?.packages,
|
|
200
208
|
model_sha256: (0, import_node_fs.existsSync)(spec.modelPath) ? sha256((0, import_node_fs.readFileSync)(spec.modelPath, "utf8")) : void 0,
|
|
201
|
-
data_sha256: sha256((0, import_core.canonicalJson)(spec.data)),
|
|
209
|
+
data_sha256: io.dataSha256 ?? sha256((0, import_core.canonicalJson)(spec.data)),
|
|
210
|
+
...io.dataFile ? { data_file: io.dataFile } : {},
|
|
202
211
|
samples_file: io.outPath,
|
|
203
212
|
started_at: startedAt,
|
|
204
213
|
elapsed_ms: elapsedMs
|
|
205
214
|
};
|
|
206
|
-
(0, import_node_fs.writeFileSync)(`${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
215
|
+
(0, import_node_fs.writeFileSync)(io.recordPath ?? `${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
207
216
|
`);
|
|
208
217
|
return {
|
|
209
218
|
status: "ok",
|
|
@@ -213,10 +222,43 @@ async function runFit(spec, resolved, io) {
|
|
|
213
222
|
elapsedMs
|
|
214
223
|
};
|
|
215
224
|
}
|
|
225
|
+
async function runFit(spec, resolved, io) {
|
|
226
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
227
|
+
const start = performance.now();
|
|
228
|
+
const runtimeRequested = spec.backend.version;
|
|
229
|
+
const ownTmp = io.tmpDir === void 0;
|
|
230
|
+
const tmp = io.tmpDir ?? makeRequestDir("fit");
|
|
231
|
+
const requestPath = (0, import_node_path3.join)(tmp, "request.json");
|
|
232
|
+
(0, import_node_fs.writeFileSync)(requestPath, JSON.stringify(fitRequest(spec, io.outPath)));
|
|
233
|
+
const args = [
|
|
234
|
+
...resolved.args,
|
|
235
|
+
"--startup-file=no",
|
|
236
|
+
`--project=${io.projectDir}`,
|
|
237
|
+
driverPath(),
|
|
238
|
+
requestPath
|
|
239
|
+
];
|
|
240
|
+
let stdout;
|
|
241
|
+
let stderr;
|
|
242
|
+
let code;
|
|
243
|
+
try {
|
|
244
|
+
({ stdout, stderr, code } = await io.spawn(resolved.command, args, {
|
|
245
|
+
onProgress: io.onProgress
|
|
246
|
+
}));
|
|
247
|
+
} finally {
|
|
248
|
+
if (ownTmp) (0, import_node_fs.rmSync)(tmp, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
const elapsedMs = Math.round(performance.now() - start);
|
|
251
|
+
if (code !== 0 || !(0, import_node_fs.existsSync)(io.outPath)) {
|
|
252
|
+
const failure = lastJsonLine(stderr);
|
|
253
|
+
const stage = toStage(failure?.stage);
|
|
254
|
+
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
255
|
+
return { status: "error", runtimeRequested, elapsedMs, stage, error };
|
|
256
|
+
}
|
|
257
|
+
return finalizeOkFit(spec, resolved, io, lastJsonLine(stdout), startedAt, elapsedMs);
|
|
258
|
+
}
|
|
216
259
|
|
|
217
260
|
// src/predict.ts
|
|
218
261
|
var import_node_fs2 = require("fs");
|
|
219
|
-
var import_node_os3 = require("os");
|
|
220
262
|
var import_node_path4 = require("path");
|
|
221
263
|
var import_core2 = require("@mcmcjs/core");
|
|
222
264
|
function predictData(spec) {
|
|
@@ -234,7 +276,8 @@ async function runPredict(spec, resolved, io) {
|
|
|
234
276
|
const start = performance.now();
|
|
235
277
|
const runtimeRequested = spec.backend.version;
|
|
236
278
|
const data = predictData(spec);
|
|
237
|
-
const
|
|
279
|
+
const ownTmp = io.tmpDir === void 0;
|
|
280
|
+
const tmp = io.tmpDir ?? makeRequestDir("predict");
|
|
238
281
|
const requestPath = (0, import_node_path4.join)(tmp, "request.json");
|
|
239
282
|
(0, import_node_fs2.writeFileSync)(
|
|
240
283
|
requestPath,
|
|
@@ -257,7 +300,14 @@ async function runPredict(spec, resolved, io) {
|
|
|
257
300
|
driverPath(),
|
|
258
301
|
requestPath
|
|
259
302
|
];
|
|
260
|
-
|
|
303
|
+
let stdout;
|
|
304
|
+
let stderr;
|
|
305
|
+
let code;
|
|
306
|
+
try {
|
|
307
|
+
({ stdout, stderr, code } = await io.spawn(resolved.command, args));
|
|
308
|
+
} finally {
|
|
309
|
+
if (ownTmp) (0, import_node_fs2.rmSync)(tmp, { recursive: true, force: true });
|
|
310
|
+
}
|
|
261
311
|
const elapsedMs = Math.round(performance.now() - start);
|
|
262
312
|
if (code !== 0 || !(0, import_node_fs2.existsSync)(io.outPath)) {
|
|
263
313
|
const failure = lastJsonLine(stderr);
|
|
@@ -306,8 +356,9 @@ async function runPredict(spec, resolved, io) {
|
|
|
306
356
|
|
|
307
357
|
// src/project.ts
|
|
308
358
|
var import_node_fs3 = require("fs");
|
|
309
|
-
var
|
|
359
|
+
var import_node_os3 = require("os");
|
|
310
360
|
var import_node_path5 = require("path");
|
|
361
|
+
var import_core3 = require("@mcmcjs/core");
|
|
311
362
|
var import_engine3 = require("@mcmcjs/engine");
|
|
312
363
|
var PACKAGES = [
|
|
313
364
|
"Turing",
|
|
@@ -320,29 +371,65 @@ var PACKAGES = [
|
|
|
320
371
|
"JSON",
|
|
321
372
|
"StableRNGs"
|
|
322
373
|
];
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
374
|
+
var VERSION_RE = /^[0-9A-Za-z.+~^=<>,\s-]+$/;
|
|
375
|
+
function validateVersionString(name, version) {
|
|
376
|
+
if (!VERSION_RE.test(version)) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`invalid version "${version}" for package ${name}; a version may contain only digits, letters, and . + ~ ^ = < > , - and spaces`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function validatePins(pins) {
|
|
383
|
+
for (const [name, version] of Object.entries(pins ?? {})) {
|
|
384
|
+
if (!PACKAGES.includes(name)) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`cannot pin unknown package "${name}"; managed packages are: ${PACKAGES.join(", ")}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
validateVersionString(name, version);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function hasPins(pins) {
|
|
393
|
+
return pins !== void 0 && Object.keys(pins).length > 0;
|
|
394
|
+
}
|
|
395
|
+
function managedProjectDir(version, pins) {
|
|
396
|
+
const base = process.env.XDG_DATA_HOME || (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".local", "share");
|
|
397
|
+
const root = (0, import_node_path5.join)(base, "mcmcjs", "julia", "env");
|
|
398
|
+
if (!version && !hasPins(pins)) return root;
|
|
399
|
+
let leaf = (version ?? "default").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
400
|
+
if (hasPins(pins)) leaf += `-${sha256((0, import_core3.canonicalJson)(pins)).slice(0, 8)}`;
|
|
401
|
+
return (0, import_node_path5.join)(root, leaf);
|
|
326
402
|
}
|
|
327
403
|
function sentinelPath(dir) {
|
|
328
404
|
return (0, import_node_path5.join)(dir, ".mcmcjs-packages.json");
|
|
329
405
|
}
|
|
330
|
-
function
|
|
406
|
+
function expectedSentinel(pins) {
|
|
407
|
+
return (0, import_core3.canonicalJson)({ packages: [...PACKAGES].sort(), pins: pins ?? {} });
|
|
408
|
+
}
|
|
409
|
+
function provisioned(dir, pins) {
|
|
331
410
|
try {
|
|
332
|
-
return (0, import_node_fs3.readFileSync)(sentinelPath(dir), "utf8").trim() ===
|
|
411
|
+
return (0, import_node_fs3.readFileSync)(sentinelPath(dir), "utf8").trim() === expectedSentinel(pins);
|
|
333
412
|
} catch {
|
|
334
413
|
return false;
|
|
335
414
|
}
|
|
336
415
|
}
|
|
337
|
-
function managedProjectReady(dir = managedProjectDir()) {
|
|
338
|
-
return (0, import_node_fs3.existsSync)((0, import_node_path5.join)(dir, "Project.toml")) && provisioned(dir);
|
|
416
|
+
function managedProjectReady(dir = managedProjectDir(), pins) {
|
|
417
|
+
return (0, import_node_fs3.existsSync)((0, import_node_path5.join)(dir, "Project.toml")) && provisioned(dir, pins);
|
|
339
418
|
}
|
|
340
|
-
|
|
341
|
-
|
|
419
|
+
function packageSpecsCode(pins) {
|
|
420
|
+
const specs = PACKAGES.map((name) => {
|
|
421
|
+
const version = pins?.[name];
|
|
422
|
+
return version ? `Pkg.PackageSpec(name=${JSON.stringify(name)}, version=${JSON.stringify(version)})` : `Pkg.PackageSpec(name=${JSON.stringify(name)})`;
|
|
423
|
+
});
|
|
424
|
+
return `[${specs.join(", ")}]`;
|
|
425
|
+
}
|
|
426
|
+
async function ensureProject(juliaBin, run = (0, import_engine3.createRunner)(30 * 6e4), dir = managedProjectDir(), pins) {
|
|
427
|
+
validatePins(pins);
|
|
428
|
+
if (managedProjectReady(dir, pins)) return dir;
|
|
342
429
|
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
343
|
-
const code = `using Pkg; Pkg.add(${
|
|
430
|
+
const code = `using Pkg; Pkg.add(${packageSpecsCode(pins)}); Pkg.precompile()`;
|
|
344
431
|
await run(juliaBin, ["--startup-file=no", `--project=${dir}`, "-e", code]);
|
|
345
|
-
(0, import_node_fs3.writeFileSync)(sentinelPath(dir),
|
|
432
|
+
(0, import_node_fs3.writeFileSync)(sentinelPath(dir), expectedSentinel(pins));
|
|
346
433
|
return dir;
|
|
347
434
|
}
|
|
348
435
|
|
|
@@ -354,11 +441,14 @@ async function runMatrix(spec, versions, io) {
|
|
|
354
441
|
let entry;
|
|
355
442
|
try {
|
|
356
443
|
const resolved = await io.resolve(version);
|
|
444
|
+
const projectDir = await io.ensure(resolved);
|
|
357
445
|
const versioned = { ...spec, backend: { ...spec.backend, version } };
|
|
358
446
|
const result = await runFit(versioned, resolved, {
|
|
359
447
|
spawn: io.spawn,
|
|
360
|
-
projectDir
|
|
361
|
-
outPath: (0, import_node_path6.join)(io.outDir, `${version}.samples.json`)
|
|
448
|
+
projectDir,
|
|
449
|
+
outPath: (0, import_node_path6.join)(io.outDir, `${version}.samples.json`),
|
|
450
|
+
dataFile: io.dataFile,
|
|
451
|
+
dataSha256: io.dataSha256
|
|
362
452
|
});
|
|
363
453
|
entry = {
|
|
364
454
|
version,
|
|
@@ -507,7 +597,7 @@ async function resolveVersion(bin, channel, run) {
|
|
|
507
597
|
`Julia version "${channel}" is not installed. Add it with: mcmc julia version add ${channel}`
|
|
508
598
|
);
|
|
509
599
|
}
|
|
510
|
-
return { command: match.File, args: match.Args ?? [] };
|
|
600
|
+
return { command: match.File, args: match.Args ?? [], version: match.Version };
|
|
511
601
|
}
|
|
512
602
|
function assertVersionsInstalled(channels, installed) {
|
|
513
603
|
const have = new Set(installed.map((v) => v.id));
|
|
@@ -519,6 +609,256 @@ function assertVersionsInstalled(channels, installed) {
|
|
|
519
609
|
);
|
|
520
610
|
}
|
|
521
611
|
}
|
|
612
|
+
|
|
613
|
+
// src/worker.ts
|
|
614
|
+
var import_node_child_process = require("child_process");
|
|
615
|
+
var import_node_fs4 = require("fs");
|
|
616
|
+
var import_node_net = require("net");
|
|
617
|
+
var import_node_path7 = require("path");
|
|
618
|
+
var import_engine4 = require("@mcmcjs/engine");
|
|
619
|
+
var START_WAIT_MS = 18e4;
|
|
620
|
+
var CONNECT_TIMEOUT_MS = 1e3;
|
|
621
|
+
var STOP_ACK_TIMEOUT_MS = 3e3;
|
|
622
|
+
function fitInactivityTimeoutMs() {
|
|
623
|
+
const fromEnv = Number(process.env.MCMC_WORKER_TIMEOUT_MS);
|
|
624
|
+
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 30 * 6e4;
|
|
625
|
+
}
|
|
626
|
+
var WorkerTimeoutError = class extends Error {
|
|
627
|
+
};
|
|
628
|
+
function workersDir() {
|
|
629
|
+
const runtime = process.env.XDG_RUNTIME_DIR;
|
|
630
|
+
const dir = runtime ? (0, import_node_path7.join)(runtime, "mcmcjs") : (0, import_node_path7.join)(sharedTmpParent(), "workers");
|
|
631
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
632
|
+
return dir;
|
|
633
|
+
}
|
|
634
|
+
function workerSocketPath(command, projectDir) {
|
|
635
|
+
return (0, import_node_path7.join)(workersDir(), `worker-${sha256(`${command}
|
|
636
|
+
${projectDir}`).slice(0, 12)}.sock`);
|
|
637
|
+
}
|
|
638
|
+
function connectOnce(path) {
|
|
639
|
+
return new Promise((resolve, reject) => {
|
|
640
|
+
const sock = (0, import_node_net.createConnection)(path);
|
|
641
|
+
const timer = setTimeout(() => {
|
|
642
|
+
sock.destroy();
|
|
643
|
+
reject(new Error("connect timeout"));
|
|
644
|
+
}, CONNECT_TIMEOUT_MS);
|
|
645
|
+
sock.once("connect", () => {
|
|
646
|
+
clearTimeout(timer);
|
|
647
|
+
resolve(sock);
|
|
648
|
+
});
|
|
649
|
+
sock.once("error", (error) => {
|
|
650
|
+
clearTimeout(timer);
|
|
651
|
+
reject(error);
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
656
|
+
function acquireStartLock(socketPath) {
|
|
657
|
+
const lock = `${socketPath}.start`;
|
|
658
|
+
try {
|
|
659
|
+
(0, import_node_fs4.mkdirSync)(lock);
|
|
660
|
+
return true;
|
|
661
|
+
} catch {
|
|
662
|
+
try {
|
|
663
|
+
if (Date.now() - (0, import_node_fs4.statSync)(lock).mtimeMs > START_WAIT_MS) {
|
|
664
|
+
(0, import_node_fs4.rmdirSync)(lock);
|
|
665
|
+
(0, import_node_fs4.mkdirSync)(lock);
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function releaseStartLock(socketPath) {
|
|
674
|
+
try {
|
|
675
|
+
(0, import_node_fs4.rmdirSync)(`${socketPath}.start`);
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function ensureWorker(resolved, projectDir, notify) {
|
|
680
|
+
const path = workerSocketPath(resolved.command, projectDir);
|
|
681
|
+
try {
|
|
682
|
+
return await connectOnce(path);
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
let spawnFailure;
|
|
686
|
+
const startedHere = acquireStartLock(path);
|
|
687
|
+
if (startedHere) {
|
|
688
|
+
try {
|
|
689
|
+
(0, import_node_fs4.rmSync)(path, { force: true });
|
|
690
|
+
notify?.("starting the Julia worker (first use loads Turing, can take a minute)...");
|
|
691
|
+
const child = (0, import_node_child_process.spawn)(
|
|
692
|
+
resolved.command,
|
|
693
|
+
[...resolved.args, "--startup-file=no", `--project=${projectDir}`, workerPath(), path],
|
|
694
|
+
{ detached: true, stdio: "ignore" }
|
|
695
|
+
);
|
|
696
|
+
child.on("error", (error) => {
|
|
697
|
+
spawnFailure = error;
|
|
698
|
+
});
|
|
699
|
+
child.on("exit", (code) => {
|
|
700
|
+
if (code !== 0 && code !== null) {
|
|
701
|
+
spawnFailure = new Error(`the worker exited with code ${code} before serving`);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
child.unref();
|
|
705
|
+
} catch (error) {
|
|
706
|
+
releaseStartLock(path);
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const deadline = Date.now() + START_WAIT_MS;
|
|
712
|
+
for (; ; ) {
|
|
713
|
+
if (spawnFailure) throw spawnFailure;
|
|
714
|
+
try {
|
|
715
|
+
return await connectOnce(path);
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
if (Date.now() > deadline) throw new Error("the Julia worker did not start in time");
|
|
719
|
+
await delay(250);
|
|
720
|
+
}
|
|
721
|
+
} finally {
|
|
722
|
+
if (startedHere) releaseStartLock(path);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeoutMs()) {
|
|
726
|
+
return new Promise((resolve, reject) => {
|
|
727
|
+
let pending = "";
|
|
728
|
+
let settled = false;
|
|
729
|
+
const settle = (fn) => {
|
|
730
|
+
if (settled) return;
|
|
731
|
+
settled = true;
|
|
732
|
+
sock.setTimeout(0);
|
|
733
|
+
fn();
|
|
734
|
+
};
|
|
735
|
+
sock.setTimeout(inactivityMs, () => {
|
|
736
|
+
const error = new WorkerTimeoutError(
|
|
737
|
+
`the worker sent nothing for ${Math.round(inactivityMs / 6e4)} minutes`
|
|
738
|
+
);
|
|
739
|
+
settle(() => {
|
|
740
|
+
sock.destroy();
|
|
741
|
+
reject(error);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
sock.on("data", (chunk) => {
|
|
745
|
+
pending += chunk.toString("utf8");
|
|
746
|
+
for (; ; ) {
|
|
747
|
+
const at = pending.indexOf("\n");
|
|
748
|
+
if (at === -1) break;
|
|
749
|
+
const line = pending.slice(0, at).trim();
|
|
750
|
+
pending = pending.slice(at + 1);
|
|
751
|
+
if (!line) continue;
|
|
752
|
+
const progress = (0, import_engine4.parseProgressLine)(line);
|
|
753
|
+
if (progress) {
|
|
754
|
+
onProgress?.(progress);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const doc = JSON.parse(line);
|
|
759
|
+
settle(() => {
|
|
760
|
+
sock.end();
|
|
761
|
+
resolve(doc);
|
|
762
|
+
});
|
|
763
|
+
return;
|
|
764
|
+
} catch {
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
sock.on("error", (error) => settle(() => reject(error)));
|
|
769
|
+
sock.on("close", () => settle(() => reject(new Error("worker closed the connection"))));
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
async function runFitViaWorker(spec, resolved, io) {
|
|
773
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
774
|
+
const start = performance.now();
|
|
775
|
+
const sock = await ensureWorker(resolved, io.projectDir, io.notify);
|
|
776
|
+
sock.write(`${JSON.stringify(fitRequest(spec, io.outPath))}
|
|
777
|
+
`);
|
|
778
|
+
const final = await readWorkerResponse(sock, io.onProgress);
|
|
779
|
+
const elapsedMs = Math.round(performance.now() - start);
|
|
780
|
+
if (final.ok !== true) {
|
|
781
|
+
if (final.stage === "worker") throw new Error(String(final.error ?? "worker error"));
|
|
782
|
+
return {
|
|
783
|
+
status: "error",
|
|
784
|
+
runtimeRequested: spec.backend.version,
|
|
785
|
+
elapsedMs,
|
|
786
|
+
stage: toStage(final.stage),
|
|
787
|
+
error: String(final.error ?? "worker error")
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
return finalizeOkFit(
|
|
791
|
+
spec,
|
|
792
|
+
resolved,
|
|
793
|
+
io,
|
|
794
|
+
final.provenance,
|
|
795
|
+
startedAt,
|
|
796
|
+
elapsedMs
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
async function runFitAuto(spec, resolved, io) {
|
|
800
|
+
if (io.daemon && process.platform !== "win32") {
|
|
801
|
+
const start = performance.now();
|
|
802
|
+
try {
|
|
803
|
+
return await runFitViaWorker(spec, resolved, io);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (error instanceof WorkerTimeoutError) {
|
|
806
|
+
return {
|
|
807
|
+
status: "error",
|
|
808
|
+
runtimeRequested: spec.backend.version,
|
|
809
|
+
elapsedMs: Math.round(performance.now() - start),
|
|
810
|
+
stage: "worker",
|
|
811
|
+
error: `${error.message}; the worker may be wedged, try mcmc daemon stop`
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
io.notify?.(
|
|
815
|
+
`worker unavailable (${error.message}); falling back to a one-shot run`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return runFit(spec, resolved, io);
|
|
820
|
+
}
|
|
821
|
+
async function listWorkers() {
|
|
822
|
+
const dir = workersDir();
|
|
823
|
+
const sockets = (0, import_node_fs4.readdirSync)(dir).filter((f) => f.startsWith("worker-") && f.endsWith(".sock"));
|
|
824
|
+
const statuses = [];
|
|
825
|
+
for (const name of sockets) {
|
|
826
|
+
const socket = (0, import_node_path7.join)(dir, name);
|
|
827
|
+
let alive = false;
|
|
828
|
+
try {
|
|
829
|
+
const sock = await connectOnce(socket);
|
|
830
|
+
sock.destroy();
|
|
831
|
+
alive = true;
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
statuses.push({ socket, alive });
|
|
835
|
+
}
|
|
836
|
+
return statuses;
|
|
837
|
+
}
|
|
838
|
+
async function stopWorker(socket) {
|
|
839
|
+
let sock;
|
|
840
|
+
try {
|
|
841
|
+
sock = await connectOnce(socket);
|
|
842
|
+
} catch {
|
|
843
|
+
(0, import_node_fs4.rmSync)(socket, { force: true });
|
|
844
|
+
return "stale";
|
|
845
|
+
}
|
|
846
|
+
const acked = await new Promise((resolve) => {
|
|
847
|
+
const timer = setTimeout(() => resolve(false), STOP_ACK_TIMEOUT_MS);
|
|
848
|
+
const finish = (ok) => {
|
|
849
|
+
clearTimeout(timer);
|
|
850
|
+
resolve(ok);
|
|
851
|
+
};
|
|
852
|
+
sock.resume();
|
|
853
|
+
sock.on("close", () => finish(true));
|
|
854
|
+
sock.on("error", () => finish(false));
|
|
855
|
+
sock.write('{"mcmcjs":"stop"}\n');
|
|
856
|
+
});
|
|
857
|
+
sock.destroy();
|
|
858
|
+
if (!acked) return "pending";
|
|
859
|
+
(0, import_node_fs4.rmSync)(socket, { force: true });
|
|
860
|
+
return "stopped";
|
|
861
|
+
}
|
|
522
862
|
// Annotate the CommonJS export names for ESM import in node:
|
|
523
863
|
0 && (module.exports = {
|
|
524
864
|
addVersion,
|
|
@@ -530,6 +870,7 @@ function assertVersionsInstalled(channels, installed) {
|
|
|
530
870
|
juliaEngine,
|
|
531
871
|
juliaupInstallCommand,
|
|
532
872
|
listVersions,
|
|
873
|
+
listWorkers,
|
|
533
874
|
managedProjectDir,
|
|
534
875
|
managedProjectReady,
|
|
535
876
|
planSetup,
|
|
@@ -538,10 +879,15 @@ function assertVersionsInstalled(channels, installed) {
|
|
|
538
879
|
resolveVersion,
|
|
539
880
|
runDoctor,
|
|
540
881
|
runFit,
|
|
882
|
+
runFitAuto,
|
|
541
883
|
runMatrix,
|
|
542
884
|
runPredict,
|
|
543
885
|
runSetup,
|
|
544
886
|
setDefaultVersion,
|
|
545
|
-
|
|
887
|
+
sharedTmpParent,
|
|
888
|
+
stopWorker,
|
|
889
|
+
updateVersion,
|
|
890
|
+
validatePins,
|
|
891
|
+
validateVersionString
|
|
546
892
|
});
|
|
547
893
|
//# sourceMappingURL=index.cjs.map
|