@ricsam/r5d-worker 0.0.110 → 0.0.111

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/mjs/main.mjs CHANGED
@@ -5,6 +5,11 @@ import { lintPlans, formatPlanLintErrors } from "./plan-lint.mjs";
5
5
  import fs from "node:fs";
6
6
  import { WorkerRecoveryStore, workerOperationDigest } from "./recovery-store.mjs";
7
7
  import { WorkerOutputWindow } from "./recovery-output-window.mjs";
8
+ import {
9
+ createWorkerJobResources,
10
+ collectWorkerInlineOutput,
11
+ WorkerJobCancelledError
12
+ } from "./job-resources.mjs";
8
13
  import {
9
14
  WORKER_RESUMABLE_PROTOCOL,
10
15
  WORKER_RECOVERY_TIMEOUT_MS
@@ -168,6 +173,7 @@ const PTY_INPUT_BUSY_GRACE_MS = 3e3;
168
173
  const PTY_FOREGROUND_POLL_MS = 1e3;
169
174
  const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
170
175
  const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
176
+ let workerJobResources;
171
177
  const activeProcesses = /* @__PURE__ */ new Map();
172
178
  const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
173
179
  const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
@@ -216,8 +222,23 @@ function renewWorkerExecutionLease(leaseMs) {
216
222
  () => {
217
223
  if (Date.now() < workerExecutionLeaseDeadline) return;
218
224
  workerLeaseExpired = true;
225
+ try {
226
+ workerJobResources?.cancelMatching((id) => id.startsWith("pty:"));
227
+ } catch (error) {
228
+ process.stderr.write(`[r5d-worker] expired PTY resource termination failed: ${error}
229
+ `);
230
+ }
231
+ closeAllPtys();
219
232
  for (const operation of recoveryStore?.operations() ?? []) {
220
- if (operation.sessionId && operation.state !== "completed") recoveryStore?.cancelSession(operation.sessionId);
233
+ if (operation.sessionId && operation.state !== "completed") {
234
+ recoveryStore?.cancelSession(operation.sessionId);
235
+ try {
236
+ workerJobResources?.cancelSession(operation.sessionId);
237
+ } catch (error) {
238
+ process.stderr.write(`[r5d-worker] expired command resource termination failed: ${error}
239
+ `);
240
+ }
241
+ }
221
242
  }
222
243
  for (const [runId, active] of activeProcesses) {
223
244
  if (!active.sessionId) continue;
@@ -1611,7 +1632,7 @@ function credentialReapContractStatus(probe) {
1611
1632
  if (process.platform !== "linux") return "direct";
1612
1633
  try {
1613
1634
  const cgroupText = fs.readFileSync("/proc/self/cgroup", "utf8");
1614
- const unit = cgroupText.match(/(?:^|\/)r5d-worker\.service(?:$|\n)/m)?.[0]?.replace(/^\//, "").trim();
1635
+ const unit = /(?:^|\/)r5d-worker\.service(?:\/|$|\n)/m.test(cgroupText) ? "r5d-worker.service" : void 0;
1615
1636
  if (unit !== "r5d-worker.service") {
1616
1637
  return SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(process.env.INVOCATION_ID ?? "") ? "systemd_unverified" : "direct";
1617
1638
  }
@@ -1645,7 +1666,7 @@ function credentialReapContractStatus(probe) {
1645
1666
  }
1646
1667
  }
1647
1668
  if (resolved.platform !== "linux") return "direct";
1648
- if (!/(?:^|\/)r5d-worker\.service(?:$|\n)/m.test(resolved.cgroupText)) {
1669
+ if (!/(?:^|\/)r5d-worker\.service(?:\/|$|\n)/m.test(resolved.cgroupText)) {
1649
1670
  return SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(resolved.invocationId ?? "") ? "systemd_unverified" : "direct";
1650
1671
  }
1651
1672
  if (!SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(resolved.invocationId ?? "")) return "systemd_unverified";
@@ -2027,12 +2048,6 @@ function resolveCommandCwd(branchPath, cwd) {
2027
2048
  }
2028
2049
  return resolved;
2029
2050
  }
2030
- async function collectStream(stream) {
2031
- if (!stream) {
2032
- return "";
2033
- }
2034
- return await new Response(stream).text();
2035
- }
2036
2051
  async function streamCommandOutput(stream, onData) {
2037
2052
  if (!stream) {
2038
2053
  return;
@@ -2753,8 +2768,37 @@ async function prepareShellEnvForTarget(input) {
2753
2768
  ...targetProcessEnv(input.rootDir, input.target)
2754
2769
  };
2755
2770
  }
2771
+ async function releaseWorkerJobResources(resources) {
2772
+ if (!resources) return;
2773
+ try {
2774
+ await resources.release();
2775
+ } catch (error) {
2776
+ process.stderr.write(`[r5d-worker] job resource cleanup failed: ${error instanceof Error ? error.message : String(error)}
2777
+ `);
2778
+ }
2779
+ }
2780
+ async function acquireWorkerCommandResources(ws, message, assertAdmission) {
2781
+ const id = message.type === "pty_open" ? `pty:${message.ptyId}` : message.runId;
2782
+ const operationId = message.type === "pty_open" ? id : `exec:${id}`;
2783
+ sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: true });
2784
+ try {
2785
+ return await workerJobResources.acquire({
2786
+ id,
2787
+ lane: message.type === "exec" && message.workspaceEffect === "none" ? "utility" : "general",
2788
+ ..."sessionId" in message && message.sessionId ? { sessionId: message.sessionId } : {},
2789
+ assertAdmission: () => {
2790
+ assertAdmission();
2791
+ if (cancelledProcessRuns.has(id)) throw new WorkerJobCancelledError();
2792
+ }
2793
+ });
2794
+ } finally {
2795
+ sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: false });
2796
+ }
2797
+ }
2756
2798
  async function executeCommand(input) {
2757
2799
  const startedAt = Date.now();
2800
+ const outputAbort = new AbortController();
2801
+ let outputReaders = [];
2758
2802
  let timeout;
2759
2803
  let spawnedProcess;
2760
2804
  let preparationSettled = false;
@@ -2775,7 +2819,7 @@ async function executeCommand(input) {
2775
2819
  });
2776
2820
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
2777
2821
  input.assertAdmission();
2778
- const subprocess = Bun.spawn(input.message.argv, {
2822
+ const subprocess = Bun.spawn(input.resources.wrap(input.message.argv), {
2779
2823
  cwd,
2780
2824
  stdout: "pipe",
2781
2825
  stderr: "pipe",
@@ -2803,6 +2847,7 @@ async function executeCommand(input) {
2803
2847
  settlePreparation();
2804
2848
  if (input.message.timeoutMs) {
2805
2849
  timeout = setTimeout(() => {
2850
+ input.resources.kill();
2806
2851
  void terminateProcessTree(subprocess).catch((error) => {
2807
2852
  process.stderr.write(
2808
2853
  `[r5d-worker] failed to terminate timed-out command ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
@@ -2811,21 +2856,26 @@ async function executeCommand(input) {
2811
2856
  });
2812
2857
  }, input.message.timeoutMs);
2813
2858
  }
2814
- const [stdout, stderr, exitCode] = await Promise.all([
2815
- collectStream(subprocess.stdout),
2816
- collectStream(subprocess.stderr),
2817
- subprocess.exited
2818
- ]);
2859
+ outputReaders = [
2860
+ collectWorkerInlineOutput(subprocess.stdout, void 0, outputAbort.signal),
2861
+ collectWorkerInlineOutput(subprocess.stderr, void 0, outputAbort.signal)
2862
+ ];
2863
+ const [stdout, stderr, exitCode] = await Promise.all([outputReaders[0], outputReaders[1], subprocess.exited]);
2819
2864
  return {
2820
2865
  type: "exec_result",
2821
2866
  requestId: input.message.requestId,
2822
2867
  stdout,
2823
2868
  stderr,
2824
2869
  exitCode,
2825
- durationMs: Date.now() - startedAt
2870
+ durationMs: Date.now() - startedAt,
2871
+ ...input.resources.memoryLimitExceeded() ? {
2872
+ error: `Command exceeded its worker memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.`
2873
+ } : {}
2826
2874
  };
2827
2875
  } catch (error) {
2876
+ outputAbort.abort(error);
2828
2877
  if (error instanceof StaleWorkerAdmissionError) throw error;
2878
+ input.resources.kill();
2829
2879
  return {
2830
2880
  type: "exec_result",
2831
2881
  requestId: input.message.requestId,
@@ -2843,6 +2893,8 @@ async function executeCommand(input) {
2843
2893
  if (spawnedProcess) {
2844
2894
  await reapCompletedCredentialBearingProcessGroup(input.message.runId, spawnedProcess);
2845
2895
  }
2896
+ outputAbort.abort();
2897
+ await Promise.allSettled(outputReaders);
2846
2898
  activeProcesses.delete(input.message.runId);
2847
2899
  }
2848
2900
  }
@@ -2880,7 +2932,7 @@ async function executeStreamingCommand(input) {
2880
2932
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
2881
2933
  const interactive = input.message.interactive === true;
2882
2934
  input.assertAdmission();
2883
- const subprocess = Bun.spawn(input.message.argv, {
2935
+ const subprocess = Bun.spawn(input.resources.wrap(input.message.argv), {
2884
2936
  cwd,
2885
2937
  // Without an explicit stdin the process reads /dev/null and interactive
2886
2938
  // prompts see immediate EOF; "pipe" keeps stdin open for exec_stdin.
@@ -2923,6 +2975,7 @@ async function executeStreamingCommand(input) {
2923
2975
  if (input.message.timeoutMs) {
2924
2976
  timeout = setTimeout(() => {
2925
2977
  timedOut = true;
2978
+ input.resources.kill();
2926
2979
  closeProcessStdin(activeProcesses.get(input.message.runId));
2927
2980
  void terminateProcessTree(subprocess).catch((error) => {
2928
2981
  process.stderr.write(
@@ -2951,6 +3004,16 @@ async function executeStreamingCommand(input) {
2951
3004
  });
2952
3005
  })
2953
3006
  ]);
3007
+ if (input.resources.memoryLimitExceeded()) {
3008
+ sendWorkerMessage(input.ws, {
3009
+ type: "exec_output",
3010
+ runId: input.message.runId,
3011
+ stream: "stderr",
3012
+ data: `
3013
+ [r5d-worker] Command exceeded its memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.
3014
+ `
3015
+ });
3016
+ }
2954
3017
  const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
2955
3018
  const terminal = {
2956
3019
  type: "exec_exit",
@@ -3380,6 +3443,7 @@ async function openPty(input) {
3380
3443
  }) : {};
3381
3444
  const targetEnv = targetProcessEnv(input.rootDir, input.resolvedTarget.target);
3382
3445
  const shell = resolveHostShell(input.message.command);
3446
+ const shellArgv = input.resources.wrap([shell.file, ...shell.args]);
3383
3447
  const outputCoalescer = createPtyOutputCoalescer({
3384
3448
  send: (data) => {
3385
3449
  sendWorkerMessage(input.ws, {
@@ -3399,8 +3463,8 @@ async function openPty(input) {
3399
3463
  };
3400
3464
  try {
3401
3465
  const ptyProcess = createNodePtyBridge({
3402
- file: shell.file,
3403
- args: shell.args,
3466
+ file: shellArgv[0],
3467
+ args: shellArgv.slice(1),
3404
3468
  ptyOptions: {
3405
3469
  name: "xterm-256color",
3406
3470
  cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
@@ -3429,11 +3493,18 @@ async function openPty(input) {
3429
3493
  outputCoalescer.push(data);
3430
3494
  },
3431
3495
  onExit: (event) => {
3496
+ if (input.resources.memoryLimitExceeded())
3497
+ outputCoalescer.push(
3498
+ `\r
3499
+ [r5d-worker] Terminal exceeded its memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.\r
3500
+ `
3501
+ );
3432
3502
  outputCoalescer.flush();
3433
3503
  input.releaseWorkspaceMutation?.();
3434
3504
  activePtys.delete(input.message.ptyId);
3435
3505
  cleanupEnvFiles();
3436
3506
  input.onTerminal?.();
3507
+ void releaseWorkerJobResources(input.resources);
3437
3508
  sendWorkerMessage(input.ws, {
3438
3509
  type: "pty_exit",
3439
3510
  ptyId: input.message.ptyId,
@@ -3447,6 +3518,7 @@ async function openPty(input) {
3447
3518
  activePtys.delete(input.message.ptyId);
3448
3519
  cleanupEnvFiles();
3449
3520
  input.onTerminal?.();
3521
+ void releaseWorkerJobResources(input.resources);
3450
3522
  sendWorkerMessage(input.ws, {
3451
3523
  type: "pty_error",
3452
3524
  requestId: input.message.requestId,
@@ -3496,6 +3568,7 @@ function resizePty(message) {
3496
3568
  ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
3497
3569
  }
3498
3570
  function closePty(message) {
3571
+ workerJobResources?.cancel(`pty:${message.ptyId}`);
3499
3572
  const ptyProcess = activePtys.get(message.ptyId);
3500
3573
  if (!ptyProcess) {
3501
3574
  return;
@@ -3514,7 +3587,7 @@ function closePtysBestEffort(ptys, reportFailure) {
3514
3587
  function closeAllPtys() {
3515
3588
  closePtysBestEffort(activePtys, (ptyId, error) => {
3516
3589
  process.stderr.write(
3517
- `[r5d-worker] failed to close PTY ${ptyId} after control disconnect: ${error instanceof Error ? error.message : String(error)}
3590
+ `[r5d-worker] failed to close PTY ${ptyId} after execution lease expiry: ${error instanceof Error ? error.message : String(error)}
3518
3591
  `
3519
3592
  );
3520
3593
  });
@@ -3587,6 +3660,11 @@ async function startWorker(options, projectRuntime = {
3587
3660
  initializeCredentialStoreDirectoryOnce();
3588
3661
  const label = requireValue(options.label, "Missing required --label <label>");
3589
3662
  validateLabel(label);
3663
+ if (!workerJobResources) {
3664
+ workerJobResources = createWorkerJobResources();
3665
+ process.stdout.write(`[r5d-worker] job resources: ${workerJobResources.isolation}; ${JSON.stringify(workerJobResources.budget)}
3666
+ `);
3667
+ }
3590
3668
  const rootDir = path.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
3591
3669
  recoveryStore ??= new WorkerRecoveryStore(
3592
3670
  path.join(rootDir, ".worker-recovery", createHash("sha256").update(`${baseUrl}\0${label}`).digest("hex"), "protocol.sqlite")
@@ -5791,6 +5869,7 @@ async function startWorker(options, projectRuntime = {
5791
5869
  }
5792
5870
  if (message.type === "cancel_session") {
5793
5871
  recoveryStore.cancelSession(message.sessionId);
5872
+ workerJobResources?.cancelSession(message.sessionId);
5794
5873
  await Promise.all(
5795
5874
  [...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
5796
5875
  cancelledProcessRuns.add(runId);
@@ -6277,8 +6356,14 @@ async function startWorker(options, projectRuntime = {
6277
6356
  return;
6278
6357
  }
6279
6358
  if (message.type === "cancel") {
6359
+ let resourceCancellationError;
6360
+ try {
6361
+ workerJobResources?.cancel(message.runId);
6362
+ } catch (error) {
6363
+ resourceCancellationError = error instanceof Error ? error.message : String(error);
6364
+ }
6280
6365
  const active = activeProcesses.get(message.runId);
6281
- let cancelled = true;
6366
+ let cancelled = resourceCancellationError === void 0;
6282
6367
  let cancelMessage;
6283
6368
  if (active) {
6284
6369
  try {
@@ -6293,6 +6378,7 @@ async function startWorker(options, projectRuntime = {
6293
6378
  cancelledProcessRuns.add(message.runId);
6294
6379
  cancelMessage = "Cancellation queued before command start";
6295
6380
  }
6381
+ if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
6296
6382
  sendSerializedWorkerMessage(
6297
6383
  ws,
6298
6384
  JSON.stringify({
@@ -6379,13 +6465,16 @@ async function startWorker(options, projectRuntime = {
6379
6465
  });
6380
6466
  return;
6381
6467
  }
6382
- await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
6383
- workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
6384
- if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
6385
- });
6468
+ let resources;
6469
+ let resourcesTransferred = false;
6386
6470
  let releaseWorkspaceMutation;
6387
6471
  let mutationLeaseTransferred = false;
6388
6472
  try {
6473
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6474
+ await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
6475
+ workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
6476
+ if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
6477
+ });
6389
6478
  releaseWorkspaceMutation = await acquireWorkspaceCommandMutation(message.target, workspaceSyncSingleFlight);
6390
6479
  if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
6391
6480
  throw new Error(
@@ -6396,6 +6485,7 @@ async function startWorker(options, projectRuntime = {
6396
6485
  process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
6397
6486
  `);
6398
6487
  await openPty({
6488
+ resources,
6399
6489
  ws,
6400
6490
  message,
6401
6491
  resolvedTarget,
@@ -6408,6 +6498,7 @@ async function startWorker(options, projectRuntime = {
6408
6498
  }
6409
6499
  } : {}
6410
6500
  });
6501
+ resourcesTransferred = true;
6411
6502
  mutationLeaseTransferred = releaseWorkspaceMutation !== void 0;
6412
6503
  } catch (error) {
6413
6504
  sendWorkerMessageFromCurrentSource(ws, {
@@ -6419,6 +6510,7 @@ async function startWorker(options, projectRuntime = {
6419
6510
  } finally {
6420
6511
  workspaceSyncPriorityPtyTargets.delete(message.ptyId);
6421
6512
  if (!mutationLeaseTransferred) releaseWorkspaceMutation?.();
6513
+ if (!resourcesTransferred) await releaseWorkerJobResources(resources);
6422
6514
  }
6423
6515
  return;
6424
6516
  }
@@ -6477,9 +6569,11 @@ async function startWorker(options, projectRuntime = {
6477
6569
  return;
6478
6570
  }
6479
6571
  let result;
6572
+ let resources;
6480
6573
  let targetReserved = false;
6481
6574
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
6482
6575
  try {
6576
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6483
6577
  if (hasWorkspaceEffect) {
6484
6578
  await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
6485
6579
  workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
@@ -6497,6 +6591,7 @@ async function startWorker(options, projectRuntime = {
6497
6591
  process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
6498
6592
  `);
6499
6593
  return await executeCommand({
6594
+ resources,
6500
6595
  message,
6501
6596
  resolvedTarget,
6502
6597
  baseUrl,
@@ -6532,6 +6627,8 @@ async function startWorker(options, projectRuntime = {
6532
6627
  };
6533
6628
  } finally {
6534
6629
  if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
6630
+ await releaseWorkerJobResources(resources);
6631
+ cancelledProcessRuns.delete(message.runId);
6535
6632
  }
6536
6633
  if (infrastructureStoppedRunIds.has(message.runId)) {
6537
6634
  recoveryStore.unknown(message.requestId);
@@ -6563,12 +6660,14 @@ async function startWorker(options, projectRuntime = {
6563
6660
  runId: message.runId
6564
6661
  });
6565
6662
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
6663
+ let resources;
6566
6664
  const runCommand = async () => {
6567
6665
  try {
6568
6666
  const resolvedTarget = resolveMessageTarget(message.target);
6569
6667
  process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
6570
6668
  `);
6571
6669
  await executeStreamingCommand({
6670
+ resources,
6572
6671
  ws,
6573
6672
  message,
6574
6673
  resolvedTarget,
@@ -6602,6 +6701,7 @@ async function startWorker(options, projectRuntime = {
6602
6701
  const execution = (async () => {
6603
6702
  let targetReserved = false;
6604
6703
  try {
6704
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6605
6705
  if (hasWorkspaceEffect) {
6606
6706
  await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
6607
6707
  workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
@@ -6617,6 +6717,8 @@ async function startWorker(options, projectRuntime = {
6617
6717
  });
6618
6718
  } finally {
6619
6719
  if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
6720
+ await releaseWorkerJobResources(resources);
6721
+ cancelledProcessRuns.delete(message.runId);
6620
6722
  }
6621
6723
  })();
6622
6724
  void execution.catch((error) => {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.110",
3
+ "version": "0.0.111",
4
4
  "type": "module"
5
5
  }
@@ -0,0 +1,61 @@
1
+ export declare const WORKER_INLINE_OUTPUT_MAX_BYTES: number;
2
+ /** Inline RPC output lives in the control process, outside the child's cgroup. */
3
+ export declare function collectWorkerInlineOutput(stream: ReadableStream<Uint8Array> | null, maxBytes?: number, signal?: AbortSignal): Promise<string>;
4
+ export type WorkerJobLane = "general" | "utility";
5
+ export type WorkerJobBudget = {
6
+ aggregateBytes: number;
7
+ generalBytes: number;
8
+ utilityBytes: number;
9
+ generalSlots: number;
10
+ utilitySlots: number;
11
+ queueTimeoutMs: number;
12
+ maxQueued: number;
13
+ pidsPerJob: number;
14
+ };
15
+ export declare function workerJobBudget(effectiveMemoryBytes: number, env?: NodeJS.ProcessEnv): WorkerJobBudget;
16
+ export declare function workerDelegatedCgroupRoot(cgroupText: string, mount?: string): string | undefined;
17
+ export declare function effectiveWorkerMemory(root: string | undefined, hostBytes?: number, mount?: string): number;
18
+ export declare class WorkerJobCapacityError extends Error {
19
+ constructor();
20
+ }
21
+ export declare class WorkerJobCancelledError extends Error {
22
+ constructor();
23
+ }
24
+ type Acquisition = {
25
+ id: string;
26
+ lane: WorkerJobLane;
27
+ sessionId?: string;
28
+ assertAdmission: () => void;
29
+ };
30
+ export type WorkerJobLease = {
31
+ readonly cgroupPath?: string;
32
+ readonly memoryBytes: number;
33
+ wrap(argv: string[]): string[];
34
+ kill(): void;
35
+ memoryLimitExceeded(): boolean;
36
+ release(): Promise<void>;
37
+ };
38
+ /** One controller per runtime, shared by every project and socket generation. */
39
+ export declare class WorkerJobResourceController {
40
+ readonly budget: WorkerJobBudget;
41
+ readonly isolation: "cgroup-v2" | "concurrency-only";
42
+ private readonly jobsRoot?;
43
+ private readonly active;
44
+ private readonly waiters;
45
+ private timer?;
46
+ constructor(budget: WorkerJobBudget, delegatedRoot?: string);
47
+ get status(): {
48
+ isolation: string;
49
+ active: number;
50
+ queued: number;
51
+ };
52
+ acquire(input: Acquisition): Promise<WorkerJobLease>;
53
+ cancel(id: string): void;
54
+ cancelSession(sessionId: string): void;
55
+ cancelMatching(matches: (id: string, sessionId?: string) => boolean): void;
56
+ private available;
57
+ private drain;
58
+ private start;
59
+ }
60
+ export declare function createWorkerJobResources(env?: NodeJS.ProcessEnv): WorkerJobResourceController;
61
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.110",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/main.cjs",
6
6
  "module": "./dist/mjs/main.mjs",