@ai-ecoverse/slicc-kernel 1.69.0 → 1.70.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/README.md CHANGED
@@ -254,7 +254,7 @@ The module's default export (or its `main` export) is called with a context and
254
254
  - `signals`: `on(signal, handler)` catches a signal (by number or as `'SIGINT'`), `ignore(signal)` ignores it and `reset(signal)` restores its default action. A call blocked in a read or write is restarted after a handler runs; signals without a handler keep their default actions, so an uncaught `SIGINT` ends the program with 130 and `SIGKILL` always does;
255
255
  - `sync`: `read`, `write`, `close`, `open` (whose files' methods are synchronous too) and `fs`, made synchronously, for code that cannot `await`, such as a compiled program's file system or a `readFileSync`. A synchronous call holds the worker until the kernel answers, so nothing else of the program runs meanwhile, timers and callbacks included; a signal that arrives during a blocked read or write runs its handler, and the call is then restarted. Descriptors are shared with the rest of the context, so a file opened with `sync.open` can be closed with `close`, and the other way around; `sync.run` and `sync.exit` are described next;
256
256
  - `sync.run({ argv, env, cwd, input, stdin, stdout, stderr, maxBuffer, timeout, killSignal })` runs a command to its end and answers `{ pid, status, signal, stdout, stderr, error }`, as Node's `spawnSync` does. It suits `execSync`, `spawnSync` and `execFileSync`. The kernel does the whole run while the program's worker waits; other processes go on.
257
- - Each stdio is `'pipe'` (the default), `'ignore'` or `'inherit'` (the program's own, a terminal included). With `'pipe'`, stdin gets `input` (bytes, or a string as UTF-8) and then its end, so `cat` stops; `stdout` and `stderr` come back as bytes and are absent for the other choices. Both are drained while the child runs, so a child that writes more than a pipe holds never stalls.
257
+ - Each stdio is `'pipe'` (the default), `'ignore'`, `'inherit'` (the program's own, a terminal included), or one of the program's descriptors by number, which the child gets as it is (two slots on one descriptor share it, so their writes keep their order). `stderr: 'stdout'` sends stderr to whatever stdout is, the stdout capture included, in the order the child writes. With `'pipe'`, stdin gets `input` (bytes, or a string as UTF-8) and then its end, so `cat` stops; `stdout` and `stderr` come back as bytes and are absent for the other choices. Both are drained while the child runs, so a child that writes more than a pipe holds never stalls. As in Node, the call returns once the child has exited and every captured stream has reached its end, so output a background grandchild writes later is still collected; `timeout` bounds that wait too. Once the outcome is decided (the end of output, an overflow or a timeout), the captured streams are closed, so a writer still left on them gets `EPIPE` (and `SIGPIPE`) instead of running on.
258
258
  - `status` is the exit status, or `null` with `signal` set when a signal ended the child. A non-zero status is not an error. A command that cannot start (it does not exist, or is not executable) answers `error: { code: 'ENOENT' }` (or the errno), `status: null` and `pid: 0` instead of throwing.
259
259
  - More than `maxBuffer` bytes on `stdout` or `stderr` (1 MiB by default) sends the child `killSignal` (`SIGTERM` by default), keeps the first `maxBuffer` bytes, and answers `error: { code: 'ENOBUFS' }`. The kernel keeps at most 64 MiB of each stream whatever `maxBuffer` says, `Infinity` included, since the buffers live in the kernel worker that all processes share. `timeout` (ms) sends `killSignal` when it runs out and answers `error: { code: 'ETIMEDOUT' }`. A child that ignores `killSignal` is still waited for, as in Node.
260
260
  - The child is in the program's process group, so `^C` reaches it. The kernel reaps it, so it leaves no zombie, and its CPU time counts as the program's children's. A signal the program catches does not cut the run short; its handler runs when the call returns. A signal that kills the program kills it as usual;
@@ -6960,6 +6960,18 @@ function capturing(chunks, max = Number.POSITIVE_INFINITY, overflow) {
6960
6960
  size += bytes.length;
6961
6961
  };
6962
6962
  }
6963
+ function captureFile(onData, control) {
6964
+ if (!control) return sinkFile(onData);
6965
+ control.opened = true;
6966
+ return new OpenFile({
6967
+ write: async (bytes) => {
6968
+ if (control.shut) throw new KernelError("EPIPE");
6969
+ onData(bytes.slice());
6970
+ return bytes.length;
6971
+ },
6972
+ close: () => control.ended()
6973
+ });
6974
+ }
6963
6975
  function concat3(chunks) {
6964
6976
  const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
6965
6977
  let offset = 0;
@@ -6994,7 +7006,12 @@ var ChildTable = class {
6994
7006
  const fds = new FdTable();
6995
7007
  const captured = /* @__PURE__ */ new Map();
6996
7008
  try {
6997
- for (const [n, slot2] of stdio.entries()) fds.installAt(n, this.openSlot(slot2, n, captured));
7009
+ for (const [n, slot2] of stdio.entries()) {
7010
+ fds.installAt(
7011
+ n,
7012
+ "sameAs" in slot2 ? fds.get(slot2.sameAs).retain() : this.openSlot(slot2, n, captured)
7013
+ );
7014
+ }
6998
7015
  for (const slot2 of inherit2) {
6999
7016
  if (slot2.fd <= 2) continue;
7000
7017
  if ("device" in slot2) {
@@ -7051,7 +7068,7 @@ var ChildTable = class {
7051
7068
  if ("capture" in slot2) {
7052
7069
  const chunks = [];
7053
7070
  captured.set(n, chunks);
7054
- return sinkFile(capturing(chunks, slot2.max, slot2.overflow));
7071
+ return captureFile(capturing(chunks, slot2.max, slot2.overflow), slot2.control);
7055
7072
  }
7056
7073
  return deviceFile("null");
7057
7074
  }
@@ -10558,9 +10575,11 @@ function capLimit(maxBuffer) {
10558
10575
  return Math.min(asked, RUN_CAPTURE_CAP);
10559
10576
  }
10560
10577
  function slot(how, n, extra) {
10578
+ if (how === "stdout") return { sameAs: 1 };
10579
+ if (typeof how === "number") return { fd: how };
10561
10580
  if (how === "inherit") return { fd: n };
10562
10581
  if (how === "ignore") return { none: true };
10563
- return extra;
10582
+ return extra();
10564
10583
  }
10565
10584
  function frame2(outcome, out, err) {
10566
10585
  const head = new TextEncoder().encode(JSON.stringify(outcome));
@@ -10590,12 +10609,20 @@ async function runChild(req, deps) {
10590
10609
  if (failure2 !== void 0) return;
10591
10610
  failure2 = code;
10592
10611
  if (pid !== 0) kill();
10612
+ decided.resolve();
10613
+ };
10614
+ const controls = [];
10615
+ const capture = () => {
10616
+ const { promise, resolve } = Promise.withResolvers();
10617
+ const control = { shut: false, ended: resolve, eof: promise };
10618
+ controls.push(control);
10619
+ return { capture: true, max, overflow: () => stop("ENOBUFS"), control };
10593
10620
  };
10594
- const capture = () => ({ capture: true, max, overflow: () => stop("ENOBUFS") });
10621
+ const decided = Promise.withResolvers();
10595
10622
  const stdio = [
10596
- slot(req.stdin, 0, { input: req.input ?? new Uint8Array(0) }),
10597
- slot(req.stdout, 1, capture()),
10598
- slot(req.stderr, 2, capture())
10623
+ slot(req.stdin, 0, () => ({ input: req.input ?? new Uint8Array(0) })),
10624
+ slot(req.stdout, 1, capture),
10625
+ slot(req.stderr, 2, capture)
10599
10626
  ];
10600
10627
  try {
10601
10628
  pid = await deps.children.spawn(
@@ -10612,8 +10639,11 @@ async function runChild(req, deps) {
10612
10639
  let status;
10613
10640
  try {
10614
10641
  status = await reaped(deps, pid);
10642
+ const open = controls.filter((c) => c.opened);
10643
+ await Promise.race([Promise.all(open.map((c) => c.eof)), decided.promise]);
10615
10644
  } finally {
10616
10645
  clearTimeout(timer);
10646
+ for (const control of controls) control.shut = true;
10617
10647
  }
10618
10648
  const out = deps.children.captured(pid, 1);
10619
10649
  const err = deps.children.captured(pid, 2);