@mcmcjs/julia 0.6.1 → 0.7.1
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/README.md +10 -4
- package/dist/fitlib.jl +94 -6
- package/dist/index.cjs +190 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +169 -36
- package/dist/index.js.map +1 -1
- package/dist/julia-env/Manifest.toml +1895 -0
- package/dist/julia-env/Project.toml +10 -0
- package/dist/worker.jl +8 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
gcVersions: () => gcVersions,
|
|
29
29
|
juliaEngine: () => juliaEngine,
|
|
30
30
|
juliaupInstallCommand: () => juliaupInstallCommand,
|
|
31
|
+
killWorker: () => killWorker,
|
|
31
32
|
listVersions: () => listVersions,
|
|
32
33
|
listWorkers: () => listWorkers,
|
|
33
34
|
managedProjectDir: () => managedProjectDir,
|
|
@@ -110,12 +111,13 @@ var juliaEngine = {
|
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
// src/fit.ts
|
|
113
|
-
var
|
|
114
|
+
var import_node_fs2 = require("fs");
|
|
114
115
|
var import_node_path3 = require("path");
|
|
115
116
|
var import_core = require("@mcmcjs/core");
|
|
116
117
|
|
|
117
118
|
// src/runner-common.ts
|
|
118
119
|
var import_node_crypto = require("crypto");
|
|
120
|
+
var import_node_fs = require("fs");
|
|
119
121
|
var import_node_os2 = require("os");
|
|
120
122
|
var import_node_path2 = require("path");
|
|
121
123
|
var import_node_url = require("url");
|
|
@@ -123,11 +125,19 @@ function sharedTmpParent() {
|
|
|
123
125
|
const uid = typeof process.getuid === "function" ? `-${process.getuid()}` : "";
|
|
124
126
|
return (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `mcmcjs${uid}`);
|
|
125
127
|
}
|
|
128
|
+
function driverFile(name) {
|
|
129
|
+
const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
130
|
+
const beside = (0, import_node_path2.join)(here, name);
|
|
131
|
+
return (0, import_node_fs.existsSync)(beside) ? beside : (0, import_node_path2.join)(here, "driver", name);
|
|
132
|
+
}
|
|
126
133
|
function driverPath() {
|
|
127
|
-
return (
|
|
134
|
+
return driverFile("driver.jl");
|
|
128
135
|
}
|
|
129
136
|
function workerPath() {
|
|
130
|
-
return (
|
|
137
|
+
return driverFile("worker.jl");
|
|
138
|
+
}
|
|
139
|
+
function pinnedEnvDir() {
|
|
140
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "julia-env");
|
|
131
141
|
}
|
|
132
142
|
function sha256(text) {
|
|
133
143
|
return (0, import_node_crypto.createHash)("sha256").update(text).digest("hex");
|
|
@@ -160,10 +170,10 @@ function toStage(value) {
|
|
|
160
170
|
// src/fit.ts
|
|
161
171
|
function makeRequestDir(kind) {
|
|
162
172
|
const parent = sharedTmpParent();
|
|
163
|
-
(0,
|
|
164
|
-
return (0,
|
|
173
|
+
(0, import_node_fs2.mkdirSync)(parent, { recursive: true });
|
|
174
|
+
return (0, import_node_fs2.mkdtempSync)((0, import_node_path3.join)(parent, `${kind}-`));
|
|
165
175
|
}
|
|
166
|
-
function fitRequest(spec, outPath) {
|
|
176
|
+
function fitRequest(spec, outPath, opts) {
|
|
167
177
|
return {
|
|
168
178
|
schema_version: spec.schema_version,
|
|
169
179
|
backend: { id: spec.backend.id },
|
|
@@ -171,12 +181,13 @@ function fitRequest(spec, outPath) {
|
|
|
171
181
|
data: spec.data,
|
|
172
182
|
sampler: spec.sampler,
|
|
173
183
|
seed: spec.seed,
|
|
174
|
-
out: outPath
|
|
184
|
+
out: outPath,
|
|
185
|
+
...opts?.streamDraws ? { stream_draws: true } : {}
|
|
175
186
|
};
|
|
176
187
|
}
|
|
177
188
|
function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
|
|
178
189
|
const runtimeRequested = spec.backend.version;
|
|
179
|
-
if (!(0,
|
|
190
|
+
if (!(0, import_node_fs2.existsSync)(io.outPath)) {
|
|
180
191
|
return {
|
|
181
192
|
status: "error",
|
|
182
193
|
runtimeRequested,
|
|
@@ -186,7 +197,7 @@ function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
|
|
|
186
197
|
};
|
|
187
198
|
}
|
|
188
199
|
try {
|
|
189
|
-
(0, import_core.parseSamples)((0,
|
|
200
|
+
(0, import_core.parseSamples)((0, import_node_fs2.readFileSync)(io.outPath, "utf8"));
|
|
190
201
|
} catch (error) {
|
|
191
202
|
return {
|
|
192
203
|
status: "error",
|
|
@@ -205,14 +216,14 @@ function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
|
|
|
205
216
|
runtime: { requested: runtimeRequested, actual: julia, path: resolved.command },
|
|
206
217
|
manifest_sha256: provenance?.manifest_sha256,
|
|
207
218
|
packages: provenance?.packages,
|
|
208
|
-
model_sha256: (0,
|
|
219
|
+
model_sha256: (0, import_node_fs2.existsSync)(spec.modelPath) ? sha256((0, import_node_fs2.readFileSync)(spec.modelPath, "utf8")) : void 0,
|
|
209
220
|
data_sha256: io.dataSha256 ?? sha256((0, import_core.canonicalJson)(spec.data)),
|
|
210
221
|
...io.dataFile ? { data_file: io.dataFile } : {},
|
|
211
222
|
samples_file: io.outPath,
|
|
212
223
|
started_at: startedAt,
|
|
213
224
|
elapsed_ms: elapsedMs
|
|
214
225
|
};
|
|
215
|
-
(0,
|
|
226
|
+
(0, import_node_fs2.writeFileSync)(io.recordPath ?? `${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
216
227
|
`);
|
|
217
228
|
return {
|
|
218
229
|
status: "ok",
|
|
@@ -226,10 +237,16 @@ async function runFit(spec, resolved, io) {
|
|
|
226
237
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
227
238
|
const start = performance.now();
|
|
228
239
|
const runtimeRequested = spec.backend.version;
|
|
240
|
+
if (io.signal?.aborted) {
|
|
241
|
+
return { status: "cancelled", runtimeRequested, elapsedMs: 0 };
|
|
242
|
+
}
|
|
229
243
|
const ownTmp = io.tmpDir === void 0;
|
|
230
244
|
const tmp = io.tmpDir ?? makeRequestDir("fit");
|
|
231
245
|
const requestPath = (0, import_node_path3.join)(tmp, "request.json");
|
|
232
|
-
(0,
|
|
246
|
+
(0, import_node_fs2.writeFileSync)(
|
|
247
|
+
requestPath,
|
|
248
|
+
JSON.stringify(fitRequest(spec, io.outPath, { streamDraws: io.onDraws !== void 0 }))
|
|
249
|
+
);
|
|
233
250
|
const args = [
|
|
234
251
|
...resolved.args,
|
|
235
252
|
"--startup-file=no",
|
|
@@ -240,15 +257,21 @@ async function runFit(spec, resolved, io) {
|
|
|
240
257
|
let stdout;
|
|
241
258
|
let stderr;
|
|
242
259
|
let code;
|
|
260
|
+
let cancelled;
|
|
243
261
|
try {
|
|
244
|
-
({ stdout, stderr, code } = await io.spawn(resolved.command, args, {
|
|
245
|
-
onProgress: io.onProgress
|
|
262
|
+
({ stdout, stderr, code, cancelled } = await io.spawn(resolved.command, args, {
|
|
263
|
+
onProgress: io.onProgress,
|
|
264
|
+
onDraws: io.onDraws,
|
|
265
|
+
signal: io.signal
|
|
246
266
|
}));
|
|
247
267
|
} finally {
|
|
248
|
-
if (ownTmp) (0,
|
|
268
|
+
if (ownTmp) (0, import_node_fs2.rmSync)(tmp, { recursive: true, force: true });
|
|
249
269
|
}
|
|
250
270
|
const elapsedMs = Math.round(performance.now() - start);
|
|
251
|
-
if (
|
|
271
|
+
if (cancelled) {
|
|
272
|
+
return { status: "cancelled", runtimeRequested, elapsedMs };
|
|
273
|
+
}
|
|
274
|
+
if (code !== 0 || !(0, import_node_fs2.existsSync)(io.outPath)) {
|
|
252
275
|
const failure = lastJsonLine(stderr);
|
|
253
276
|
const stage = toStage(failure?.stage);
|
|
254
277
|
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
@@ -258,7 +281,7 @@ async function runFit(spec, resolved, io) {
|
|
|
258
281
|
}
|
|
259
282
|
|
|
260
283
|
// src/predict.ts
|
|
261
|
-
var
|
|
284
|
+
var import_node_fs3 = require("fs");
|
|
262
285
|
var import_node_path4 = require("path");
|
|
263
286
|
var import_core2 = require("@mcmcjs/core");
|
|
264
287
|
function predictData(spec) {
|
|
@@ -279,7 +302,7 @@ async function runPredict(spec, resolved, io) {
|
|
|
279
302
|
const ownTmp = io.tmpDir === void 0;
|
|
280
303
|
const tmp = io.tmpDir ?? makeRequestDir("predict");
|
|
281
304
|
const requestPath = (0, import_node_path4.join)(tmp, "request.json");
|
|
282
|
-
(0,
|
|
305
|
+
(0, import_node_fs3.writeFileSync)(
|
|
283
306
|
requestPath,
|
|
284
307
|
JSON.stringify({
|
|
285
308
|
mode: "predict",
|
|
@@ -306,16 +329,16 @@ async function runPredict(spec, resolved, io) {
|
|
|
306
329
|
try {
|
|
307
330
|
({ stdout, stderr, code } = await io.spawn(resolved.command, args));
|
|
308
331
|
} finally {
|
|
309
|
-
if (ownTmp) (0,
|
|
332
|
+
if (ownTmp) (0, import_node_fs3.rmSync)(tmp, { recursive: true, force: true });
|
|
310
333
|
}
|
|
311
334
|
const elapsedMs = Math.round(performance.now() - start);
|
|
312
|
-
if (code !== 0 || !(0,
|
|
335
|
+
if (code !== 0 || !(0, import_node_fs3.existsSync)(io.outPath)) {
|
|
313
336
|
const failure = lastJsonLine(stderr);
|
|
314
337
|
const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
|
|
315
338
|
return { status: "error", runtimeRequested, elapsedMs, stage: toStage(failure?.stage), error };
|
|
316
339
|
}
|
|
317
340
|
try {
|
|
318
|
-
(0, import_core2.parseSamples)((0,
|
|
341
|
+
(0, import_core2.parseSamples)((0, import_node_fs3.readFileSync)(io.outPath, "utf8"));
|
|
319
342
|
} catch (error) {
|
|
320
343
|
return {
|
|
321
344
|
status: "error",
|
|
@@ -335,15 +358,15 @@ async function runPredict(spec, resolved, io) {
|
|
|
335
358
|
runtime: { requested: runtimeRequested, actual: julia, path: resolved.command },
|
|
336
359
|
manifest_sha256: provenance?.manifest_sha256,
|
|
337
360
|
packages: provenance?.packages,
|
|
338
|
-
model_sha256: (0,
|
|
361
|
+
model_sha256: (0, import_node_fs3.existsSync)(spec.modelPath) ? sha256((0, import_node_fs3.readFileSync)(spec.modelPath, "utf8")) : void 0,
|
|
339
362
|
data_sha256: sha256((0, import_core2.canonicalJson)(data)),
|
|
340
363
|
posterior_samples: io.samplesPath,
|
|
341
|
-
posterior_samples_sha256: (0,
|
|
364
|
+
posterior_samples_sha256: (0, import_node_fs3.existsSync)(io.samplesPath) ? sha256((0, import_node_fs3.readFileSync)(io.samplesPath, "utf8")) : void 0,
|
|
342
365
|
samples_file: io.outPath,
|
|
343
366
|
started_at: startedAt,
|
|
344
367
|
elapsed_ms: elapsedMs
|
|
345
368
|
};
|
|
346
|
-
(0,
|
|
369
|
+
(0, import_node_fs3.writeFileSync)(`${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
|
|
347
370
|
`);
|
|
348
371
|
return {
|
|
349
372
|
status: "ok",
|
|
@@ -355,7 +378,7 @@ async function runPredict(spec, resolved, io) {
|
|
|
355
378
|
}
|
|
356
379
|
|
|
357
380
|
// src/project.ts
|
|
358
|
-
var
|
|
381
|
+
var import_node_fs4 = require("fs");
|
|
359
382
|
var import_node_os3 = require("os");
|
|
360
383
|
var import_node_path5 = require("path");
|
|
361
384
|
var import_core3 = require("@mcmcjs/core");
|
|
@@ -403,18 +426,23 @@ function managedProjectDir(version, pins) {
|
|
|
403
426
|
function sentinelPath(dir) {
|
|
404
427
|
return (0, import_node_path5.join)(dir, ".mcmcjs-packages.json");
|
|
405
428
|
}
|
|
429
|
+
var PROVISION_GENERATION = 1;
|
|
406
430
|
function expectedSentinel(pins) {
|
|
407
|
-
return (0, import_core3.canonicalJson)({
|
|
431
|
+
return (0, import_core3.canonicalJson)({
|
|
432
|
+
gen: PROVISION_GENERATION,
|
|
433
|
+
packages: [...PACKAGES].sort(),
|
|
434
|
+
pins: pins ?? {}
|
|
435
|
+
});
|
|
408
436
|
}
|
|
409
437
|
function provisioned(dir, pins) {
|
|
410
438
|
try {
|
|
411
|
-
return (0,
|
|
439
|
+
return (0, import_node_fs4.readFileSync)(sentinelPath(dir), "utf8").trim() === expectedSentinel(pins);
|
|
412
440
|
} catch {
|
|
413
441
|
return false;
|
|
414
442
|
}
|
|
415
443
|
}
|
|
416
444
|
function managedProjectReady(dir = managedProjectDir(), pins) {
|
|
417
|
-
return (0,
|
|
445
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path5.join)(dir, "Project.toml")) && provisioned(dir, pins);
|
|
418
446
|
}
|
|
419
447
|
function packageSpecsCode(pins) {
|
|
420
448
|
const specs = PACKAGES.map((name) => {
|
|
@@ -423,13 +451,27 @@ function packageSpecsCode(pins) {
|
|
|
423
451
|
});
|
|
424
452
|
return `[${specs.join(", ")}]`;
|
|
425
453
|
}
|
|
426
|
-
|
|
454
|
+
function usesPinnedEnv(version, pins) {
|
|
455
|
+
return !hasPins(pins) && version === import_core3.DEFAULT_JULIA_CHANNEL && (0, import_node_fs4.existsSync)(pinnedEnvDir());
|
|
456
|
+
}
|
|
457
|
+
async function ensureProject(juliaBin, run = (0, import_engine3.createRunner)(30 * 6e4), dir = managedProjectDir(), pins, opts) {
|
|
427
458
|
validatePins(pins);
|
|
428
459
|
if (managedProjectReady(dir, pins)) return dir;
|
|
429
|
-
(0,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
460
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
461
|
+
if (usesPinnedEnv(opts?.version, pins)) {
|
|
462
|
+
(0, import_node_fs4.copyFileSync)((0, import_node_path5.join)(pinnedEnvDir(), "Project.toml"), (0, import_node_path5.join)(dir, "Project.toml"));
|
|
463
|
+
(0, import_node_fs4.copyFileSync)((0, import_node_path5.join)(pinnedEnvDir(), "Manifest.toml"), (0, import_node_path5.join)(dir, "Manifest.toml"));
|
|
464
|
+
await run(juliaBin, [
|
|
465
|
+
"--startup-file=no",
|
|
466
|
+
`--project=${dir}`,
|
|
467
|
+
"-e",
|
|
468
|
+
"using Pkg; Pkg.instantiate(); Pkg.precompile()"
|
|
469
|
+
]);
|
|
470
|
+
} else {
|
|
471
|
+
const code = `using Pkg; Pkg.add(${packageSpecsCode(pins)}); Pkg.precompile()`;
|
|
472
|
+
await run(juliaBin, ["--startup-file=no", `--project=${dir}`, "-e", code]);
|
|
473
|
+
}
|
|
474
|
+
(0, import_node_fs4.writeFileSync)(sentinelPath(dir), expectedSentinel(pins));
|
|
433
475
|
return dir;
|
|
434
476
|
}
|
|
435
477
|
|
|
@@ -463,6 +505,7 @@ async function runMatrix(spec, versions, io) {
|
|
|
463
505
|
entry = { version, status: "error", elapsedMs: 0, error: error.message };
|
|
464
506
|
}
|
|
465
507
|
entries.push(entry);
|
|
508
|
+
if (entry.status === "cancelled") break;
|
|
466
509
|
if (entry.status === "error" && !io.keepGoing) break;
|
|
467
510
|
}
|
|
468
511
|
return {
|
|
@@ -472,28 +515,38 @@ async function runMatrix(spec, versions, io) {
|
|
|
472
515
|
}
|
|
473
516
|
|
|
474
517
|
// src/setup.ts
|
|
518
|
+
var import_core4 = require("@mcmcjs/core");
|
|
475
519
|
var JULIAUP_INSTALL_URL = "https://install.julialang.org";
|
|
476
520
|
function juliaupInstallCommand(platform) {
|
|
477
521
|
if (platform === "win32") {
|
|
478
522
|
return null;
|
|
479
523
|
}
|
|
480
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
command: "sh",
|
|
526
|
+
args: [
|
|
527
|
+
"-c",
|
|
528
|
+
`curl -fsSL ${JULIAUP_INSTALL_URL} | sh -s -- --yes --default-channel ${import_core4.DEFAULT_JULIA_CHANNEL}`
|
|
529
|
+
]
|
|
530
|
+
};
|
|
481
531
|
}
|
|
482
532
|
function planSetup(report, platform) {
|
|
483
|
-
if (report.julia.found) return [];
|
|
533
|
+
if (report.julia.found && report.julia.version === import_core4.DEFAULT_JULIA_CHANNEL) return [];
|
|
484
534
|
if (report.juliaup.found) {
|
|
485
535
|
return [
|
|
486
536
|
{
|
|
487
537
|
tool: "julia",
|
|
488
|
-
label:
|
|
489
|
-
command: {
|
|
538
|
+
label: `install the pinned Julia ${import_core4.DEFAULT_JULIA_CHANNEL} via juliaup`,
|
|
539
|
+
command: {
|
|
540
|
+
command: report.juliaup.path ?? "juliaup",
|
|
541
|
+
args: ["add", import_core4.DEFAULT_JULIA_CHANNEL]
|
|
542
|
+
}
|
|
490
543
|
}
|
|
491
544
|
];
|
|
492
545
|
}
|
|
493
546
|
return [
|
|
494
547
|
{
|
|
495
548
|
tool: "juliaup",
|
|
496
|
-
label:
|
|
549
|
+
label: `install juliaup with the pinned Julia ${import_core4.DEFAULT_JULIA_CHANNEL}`,
|
|
497
550
|
command: juliaupInstallCommand(platform)
|
|
498
551
|
}
|
|
499
552
|
];
|
|
@@ -506,8 +559,8 @@ async function runSetup(options = {}) {
|
|
|
506
559
|
dryRun = false
|
|
507
560
|
} = options;
|
|
508
561
|
const before = await runDoctor(runner);
|
|
509
|
-
if (before.ready) return { ...before, steps: [] };
|
|
510
562
|
const plan = planSetup(before, platform);
|
|
563
|
+
if (plan.length === 0) return { ...before, steps: [] };
|
|
511
564
|
if (dryRun) {
|
|
512
565
|
return {
|
|
513
566
|
...before,
|
|
@@ -612,7 +665,7 @@ function assertVersionsInstalled(channels, installed) {
|
|
|
612
665
|
|
|
613
666
|
// src/worker.ts
|
|
614
667
|
var import_node_child_process = require("child_process");
|
|
615
|
-
var
|
|
668
|
+
var import_node_fs5 = require("fs");
|
|
616
669
|
var import_node_net = require("net");
|
|
617
670
|
var import_node_path7 = require("path");
|
|
618
671
|
var import_engine4 = require("@mcmcjs/engine");
|
|
@@ -625,10 +678,31 @@ function fitInactivityTimeoutMs() {
|
|
|
625
678
|
}
|
|
626
679
|
var WorkerTimeoutError = class extends Error {
|
|
627
680
|
};
|
|
681
|
+
var FitCancelledError = class extends Error {
|
|
682
|
+
};
|
|
683
|
+
function workerPidPath(socket) {
|
|
684
|
+
return `${socket}.pid`;
|
|
685
|
+
}
|
|
686
|
+
function killWorker(socket) {
|
|
687
|
+
const pidPath = workerPidPath(socket);
|
|
688
|
+
try {
|
|
689
|
+
const pid = Number((0, import_node_fs5.readFileSync)(pidPath, "utf8").trim());
|
|
690
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
691
|
+
try {
|
|
692
|
+
process.kill(-pid, "SIGKILL");
|
|
693
|
+
} catch {
|
|
694
|
+
process.kill(pid, "SIGKILL");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
699
|
+
(0, import_node_fs5.rmSync)(socket, { force: true });
|
|
700
|
+
(0, import_node_fs5.rmSync)(pidPath, { force: true });
|
|
701
|
+
}
|
|
628
702
|
function workersDir() {
|
|
629
703
|
const runtime = process.env.XDG_RUNTIME_DIR;
|
|
630
704
|
const dir = runtime ? (0, import_node_path7.join)(runtime, "mcmcjs") : (0, import_node_path7.join)(sharedTmpParent(), "workers");
|
|
631
|
-
(0,
|
|
705
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
632
706
|
return dir;
|
|
633
707
|
}
|
|
634
708
|
function workerSocketPath(command, projectDir) {
|
|
@@ -656,13 +730,13 @@ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
656
730
|
function acquireStartLock(socketPath) {
|
|
657
731
|
const lock = `${socketPath}.start`;
|
|
658
732
|
try {
|
|
659
|
-
(0,
|
|
733
|
+
(0, import_node_fs5.mkdirSync)(lock);
|
|
660
734
|
return true;
|
|
661
735
|
} catch {
|
|
662
736
|
try {
|
|
663
|
-
if (Date.now() - (0,
|
|
664
|
-
(0,
|
|
665
|
-
(0,
|
|
737
|
+
if (Date.now() - (0, import_node_fs5.statSync)(lock).mtimeMs > START_WAIT_MS) {
|
|
738
|
+
(0, import_node_fs5.rmdirSync)(lock);
|
|
739
|
+
(0, import_node_fs5.mkdirSync)(lock);
|
|
666
740
|
return true;
|
|
667
741
|
}
|
|
668
742
|
} catch {
|
|
@@ -672,21 +746,22 @@ function acquireStartLock(socketPath) {
|
|
|
672
746
|
}
|
|
673
747
|
function releaseStartLock(socketPath) {
|
|
674
748
|
try {
|
|
675
|
-
(0,
|
|
749
|
+
(0, import_node_fs5.rmdirSync)(`${socketPath}.start`);
|
|
676
750
|
} catch {
|
|
677
751
|
}
|
|
678
752
|
}
|
|
679
|
-
async function ensureWorker(resolved, projectDir, notify) {
|
|
753
|
+
async function ensureWorker(resolved, projectDir, notify, signal) {
|
|
680
754
|
const path = workerSocketPath(resolved.command, projectDir);
|
|
681
755
|
try {
|
|
682
756
|
return await connectOnce(path);
|
|
683
757
|
} catch {
|
|
684
758
|
}
|
|
759
|
+
if (signal?.aborted) throw new FitCancelledError();
|
|
685
760
|
let spawnFailure;
|
|
686
761
|
const startedHere = acquireStartLock(path);
|
|
687
762
|
if (startedHere) {
|
|
688
763
|
try {
|
|
689
|
-
(0,
|
|
764
|
+
(0, import_node_fs5.rmSync)(path, { force: true });
|
|
690
765
|
notify?.("starting the Julia worker (first use loads Turing, can take a minute)...");
|
|
691
766
|
const child = (0, import_node_child_process.spawn)(
|
|
692
767
|
resolved.command,
|
|
@@ -710,6 +785,7 @@ async function ensureWorker(resolved, projectDir, notify) {
|
|
|
710
785
|
try {
|
|
711
786
|
const deadline = Date.now() + START_WAIT_MS;
|
|
712
787
|
for (; ; ) {
|
|
788
|
+
if (signal?.aborted) throw new FitCancelledError();
|
|
713
789
|
if (spawnFailure) throw spawnFailure;
|
|
714
790
|
try {
|
|
715
791
|
return await connectOnce(path);
|
|
@@ -722,16 +798,27 @@ async function ensureWorker(resolved, projectDir, notify) {
|
|
|
722
798
|
if (startedHere) releaseStartLock(path);
|
|
723
799
|
}
|
|
724
800
|
}
|
|
725
|
-
function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeoutMs()) {
|
|
801
|
+
function readWorkerResponse(sock, onProgress, onDraws, signal, inactivityMs = fitInactivityTimeoutMs()) {
|
|
726
802
|
return new Promise((resolve, reject) => {
|
|
727
803
|
let pending = "";
|
|
728
804
|
let settled = false;
|
|
805
|
+
const onAbort = () => {
|
|
806
|
+
settle(() => {
|
|
807
|
+
sock.destroy();
|
|
808
|
+
reject(new FitCancelledError());
|
|
809
|
+
});
|
|
810
|
+
};
|
|
729
811
|
const settle = (fn) => {
|
|
730
812
|
if (settled) return;
|
|
731
813
|
settled = true;
|
|
732
814
|
sock.setTimeout(0);
|
|
815
|
+
signal?.removeEventListener("abort", onAbort);
|
|
733
816
|
fn();
|
|
734
817
|
};
|
|
818
|
+
if (signal) {
|
|
819
|
+
if (signal.aborted) onAbort();
|
|
820
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
821
|
+
}
|
|
735
822
|
sock.setTimeout(inactivityMs, () => {
|
|
736
823
|
const error = new WorkerTimeoutError(
|
|
737
824
|
`the worker sent nothing for ${Math.round(inactivityMs / 6e4)} minutes`
|
|
@@ -754,6 +841,11 @@ function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeou
|
|
|
754
841
|
onProgress?.(progress);
|
|
755
842
|
continue;
|
|
756
843
|
}
|
|
844
|
+
const batch = (0, import_engine4.parseDrawBatchLine)(line);
|
|
845
|
+
if (batch) {
|
|
846
|
+
onDraws?.(batch);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
757
849
|
try {
|
|
758
850
|
const doc = JSON.parse(line);
|
|
759
851
|
settle(() => {
|
|
@@ -772,10 +864,48 @@ function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeou
|
|
|
772
864
|
async function runFitViaWorker(spec, resolved, io) {
|
|
773
865
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
774
866
|
const start = performance.now();
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
867
|
+
if (io.signal?.aborted) {
|
|
868
|
+
return { status: "cancelled", runtimeRequested: spec.backend.version, elapsedMs: 0 };
|
|
869
|
+
}
|
|
870
|
+
let sock;
|
|
871
|
+
try {
|
|
872
|
+
sock = await ensureWorker(resolved, io.projectDir, io.notify, io.signal);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
if (error instanceof FitCancelledError) {
|
|
875
|
+
return {
|
|
876
|
+
status: "cancelled",
|
|
877
|
+
runtimeRequested: spec.backend.version,
|
|
878
|
+
elapsedMs: Math.round(performance.now() - start)
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
throw error;
|
|
882
|
+
}
|
|
883
|
+
if (io.signal?.aborted) {
|
|
884
|
+
sock.destroy();
|
|
885
|
+
return {
|
|
886
|
+
status: "cancelled",
|
|
887
|
+
runtimeRequested: spec.backend.version,
|
|
888
|
+
elapsedMs: Math.round(performance.now() - start)
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
sock.write(
|
|
892
|
+
`${JSON.stringify(fitRequest(spec, io.outPath, { streamDraws: io.onDraws !== void 0 }))}
|
|
893
|
+
`
|
|
894
|
+
);
|
|
895
|
+
let final;
|
|
896
|
+
try {
|
|
897
|
+
final = await readWorkerResponse(sock, io.onProgress, io.onDraws, io.signal);
|
|
898
|
+
} catch (error) {
|
|
899
|
+
if (error instanceof FitCancelledError) {
|
|
900
|
+
killWorker(workerSocketPath(resolved.command, io.projectDir));
|
|
901
|
+
return {
|
|
902
|
+
status: "cancelled",
|
|
903
|
+
runtimeRequested: spec.backend.version,
|
|
904
|
+
elapsedMs: Math.round(performance.now() - start)
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
throw error;
|
|
908
|
+
}
|
|
779
909
|
const elapsedMs = Math.round(performance.now() - start);
|
|
780
910
|
if (final.ok !== true) {
|
|
781
911
|
if (final.stage === "worker") throw new Error(String(final.error ?? "worker error"));
|
|
@@ -797,6 +927,9 @@ async function runFitViaWorker(spec, resolved, io) {
|
|
|
797
927
|
);
|
|
798
928
|
}
|
|
799
929
|
async function runFitAuto(spec, resolved, io) {
|
|
930
|
+
if (io.signal?.aborted) {
|
|
931
|
+
return { status: "cancelled", runtimeRequested: spec.backend.version, elapsedMs: 0 };
|
|
932
|
+
}
|
|
800
933
|
if (io.daemon && process.platform !== "win32") {
|
|
801
934
|
const start = performance.now();
|
|
802
935
|
try {
|
|
@@ -820,7 +953,7 @@ async function runFitAuto(spec, resolved, io) {
|
|
|
820
953
|
}
|
|
821
954
|
async function listWorkers() {
|
|
822
955
|
const dir = workersDir();
|
|
823
|
-
const sockets = (0,
|
|
956
|
+
const sockets = (0, import_node_fs5.readdirSync)(dir).filter((f) => f.startsWith("worker-") && f.endsWith(".sock"));
|
|
824
957
|
const statuses = [];
|
|
825
958
|
for (const name of sockets) {
|
|
826
959
|
const socket = (0, import_node_path7.join)(dir, name);
|
|
@@ -840,7 +973,7 @@ async function stopWorker(socket) {
|
|
|
840
973
|
try {
|
|
841
974
|
sock = await connectOnce(socket);
|
|
842
975
|
} catch {
|
|
843
|
-
(0,
|
|
976
|
+
(0, import_node_fs5.rmSync)(socket, { force: true });
|
|
844
977
|
return "stale";
|
|
845
978
|
}
|
|
846
979
|
const acked = await new Promise((resolve) => {
|
|
@@ -856,7 +989,7 @@ async function stopWorker(socket) {
|
|
|
856
989
|
});
|
|
857
990
|
sock.destroy();
|
|
858
991
|
if (!acked) return "pending";
|
|
859
|
-
(0,
|
|
992
|
+
(0, import_node_fs5.rmSync)(socket, { force: true });
|
|
860
993
|
return "stopped";
|
|
861
994
|
}
|
|
862
995
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -869,6 +1002,7 @@ async function stopWorker(socket) {
|
|
|
869
1002
|
gcVersions,
|
|
870
1003
|
juliaEngine,
|
|
871
1004
|
juliaupInstallCommand,
|
|
1005
|
+
killWorker,
|
|
872
1006
|
listVersions,
|
|
873
1007
|
listWorkers,
|
|
874
1008
|
managedProjectDir,
|