@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/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
- return [join(homedir(), ".juliaup", "bin", binary), binary];
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
- async function runFit(spec, resolved, io) {
100
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
101
- const start = performance.now();
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
- const tmp = io.tmpDir ?? mkdtempSync(join3(tmpdir(), "mcmcjs-fit-"));
104
- const requestPath = join3(tmp, "request.json");
105
- writeFileSync(
106
- requestPath,
107
- JSON.stringify({
108
- schema_version: spec.schema_version,
109
- backend: { id: spec.backend.id },
110
- model: { file: spec.modelPath, entry: spec.model.entry },
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, mkdtempSync as mkdtempSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
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 tmp = io.tmpDir ?? mkdtempSync2(join4(tmpdir2(), "mcmcjs-predict-"));
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
- const { stdout, stderr, code } = await io.spawn(resolved.command, args);
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
- function managedProjectDir() {
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
- return join5(base, "mcmcjs", "julia", "env");
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 provisioned(dir) {
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() === JSON.stringify([...PACKAGES].sort());
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
- async function ensureProject(juliaBin, run = createRunner3(30 * 6e4), dir = managedProjectDir()) {
298
- if (managedProjectReady(dir)) return dir;
299
- mkdirSync(dir, { recursive: true });
300
- const code = `using Pkg; Pkg.add(${JSON.stringify(PACKAGES)}); Pkg.precompile()`;
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), JSON.stringify([...PACKAGES].sort()));
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: io.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
- updateVersion
837
+ sharedTmpParent,
838
+ stopWorker,
839
+ updateVersion,
840
+ validatePins,
841
+ validateVersionString
502
842
  };
503
843
  //# sourceMappingURL=index.js.map