@ai-ecoverse/slicc-kernel 1.31.1 → 1.31.2

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
@@ -325,7 +325,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
325
325
  - `@ai-ecoverse/slicc-kernel/driver` exports the types and `fsError`.
326
326
  - **Capabilities:** `readonly`, `symlinks`, `chmod`, `ranges`, `maxIo` (the largest read or write per message), `maxFile`, `listingStats`, `linkTimes`, and `attrTtl`/`entryTtl` (in ms, default 1000). `linkTimes` promises that `setattr` with `mtime` on a symlink sets the link's own time. Without it, setting a link's own times (`touch -h`, `utimensat` with `AT_SYMLINK_NOFOLLOW`) fails with `EOPNOTSUPP` and never reaches the target. tmpfs has it; hostfs does not. Setting times through a link follows it in the kernel, so `setattr` gets the target. `ranges` (driver protocol 1.1) promises that a write lands in the file at once, with no copy of the whole file on `open` or `release`, and that `setattr` takes `size`.
327
327
  - **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
328
- - **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. Elsewhere (`fsa` mounts, `hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Both give close-to-open consistency, as NFS has.
328
+ - **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. Elsewhere (`fsa` mounts, `hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Both give close-to-open consistency, as NFS has. A process that ends without closing its files keeps what it wrote: on exit, on a trap, and when a signal kills it. For a killed WASI program, the kernel lets its worker write its changed pages back before `waitpid` reports the death. The only exception is a worker that makes no call into the kernel for 2 seconds after the signal; it is stopped without that write-back.
329
329
  - **Rules on a mount:**
330
330
  - a rename across mounts is `EXDEV`, so `mv` copies;
331
331
  - `df` reports each mount from its driver's `statfs`;
@@ -5976,6 +5976,7 @@ var SAB_I_TIMERS = 9;
5976
5976
  var SAB_I_MEMORY = 10;
5977
5977
  var SAB_I_TOTAL_HI = 11;
5978
5978
  var SAB_I_OFFSET_HI = 12;
5979
+ var SAB_I_KILLED = 13;
5979
5980
  var U32 = 2 ** 32;
5980
5981
  function storeU53(header2, lo, hi, value) {
5981
5982
  Atomics.store(header2, hi, Math.floor(value / U32));
@@ -7817,6 +7818,7 @@ function isWasmSyscall(req) {
7817
7818
  return typeof op === "string" && SYSCALL_OPS.has(op);
7818
7819
  }
7819
7820
  var MAX_READ = 1024 * 1024;
7821
+ var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
7820
7822
  var HOSTS_ONLY = new Resolver({ routes: new Routes() });
7821
7823
  var WasmProcess = class {
7822
7824
  exited = false;
@@ -7825,6 +7827,7 @@ var WasmProcess = class {
7825
7827
  ignored = 0;
7826
7828
  inherited = 0;
7827
7829
  interrupt = new AbortController();
7830
+ dying = false;
7828
7831
  execChild;
7829
7832
  execTermsig;
7830
7833
  stopped = 0;
@@ -7857,7 +7860,10 @@ var WasmProcess = class {
7857
7860
  signal(sig) {
7858
7861
  if (this.execChild !== void 0) {
7859
7862
  void Promise.resolve(this.options.kill?.(this.execChild, sig)).catch(() => void 0);
7860
- return sig === SIG.KILL ? "terminate" : "forward";
7863
+ if (sig !== SIG.KILL) return "forward";
7864
+ if (!this.options.writesBack?.(this.execChild)) return "terminate";
7865
+ this.wake?.();
7866
+ return "forward";
7861
7867
  }
7862
7868
  if (sig === SIG.KILL) return "terminate";
7863
7869
  if (sig === SIG.CONT) this.cont();
@@ -7901,14 +7907,22 @@ var WasmProcess = class {
7901
7907
  this.options.onSyscall?.(req);
7902
7908
  for (; ; ) {
7903
7909
  await this.resumed;
7910
+ if (this.dying) return DYING;
7904
7911
  const stops = this.stops;
7905
7912
  const result = await this.dispatch(req);
7913
+ if (this.dying) return DYING;
7906
7914
  const restart = !result.ok && result.errno === "EINTR" && this.stops !== stops;
7907
7915
  if (restart && !this.options.hasPending?.()) continue;
7908
7916
  await this.resumed;
7909
7917
  return result;
7910
7918
  }
7911
7919
  }
7920
+ die() {
7921
+ if (this.dying) return;
7922
+ this.dying = true;
7923
+ this.interrupt.abort();
7924
+ this.wake?.();
7925
+ }
7912
7926
  async dispatch(req) {
7913
7927
  try {
7914
7928
  if (isFdSyscall(req)) return await this.fdSyscall(req);
@@ -8369,6 +8383,7 @@ var WASM_MAX_THREADS = 256;
8369
8383
 
8370
8384
  // src/kernel/host.ts
8371
8385
  var CRASHED = 70;
8386
+ var KILL_GRACE_MS = 2e3;
8372
8387
  var descIds = /* @__PURE__ */ new WeakMap();
8373
8388
  var nextDescId = 1;
8374
8389
  function descId(file) {
@@ -8392,6 +8407,41 @@ function inheritedFds(fds) {
8392
8407
  };
8393
8408
  });
8394
8409
  }
8410
+ var Dying = class {
8411
+ ending;
8412
+ timer;
8413
+ busy = 0;
8414
+ finish;
8415
+ start(code, sig, header2, process, finish) {
8416
+ if (this.ending) return;
8417
+ this.ending = { code, sig };
8418
+ this.finish = finish;
8419
+ this.arm();
8420
+ Atomics.store(header2, SAB_I_KILLED, code);
8421
+ process.die();
8422
+ }
8423
+ async serving(work) {
8424
+ this.busy++;
8425
+ clearTimeout(this.timer);
8426
+ try {
8427
+ return await work();
8428
+ } finally {
8429
+ this.busy--;
8430
+ this.arm();
8431
+ }
8432
+ }
8433
+ arm() {
8434
+ clearTimeout(this.timer);
8435
+ if (this.busy === 0 && this.finish) this.timer = setTimeout(this.finish, KILL_GRACE_MS);
8436
+ }
8437
+ clear() {
8438
+ clearTimeout(this.timer);
8439
+ }
8440
+ status(reported) {
8441
+ if (this.ending) return [this.ending.code, this.ending.sig];
8442
+ return [typeof reported === "number" ? reported : CRASHED, void 0];
8443
+ }
8444
+ };
8395
8445
  function spawnWasmProcess(opts) {
8396
8446
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
8397
8447
  const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -8403,6 +8453,7 @@ function spawnWasmProcess(opts) {
8403
8453
  forker: opts.forker,
8404
8454
  fs: opts.fs,
8405
8455
  kill: opts.kill,
8456
+ ...opts.writesBack ? { writesBack: opts.writesBack } : {},
8406
8457
  processes: opts.processes,
8407
8458
  openFiles: opts.openFiles,
8408
8459
  ...opts.nodes ? { nodes: opts.nodes } : {},
@@ -8433,18 +8484,16 @@ function spawnWasmProcess(opts) {
8433
8484
  renamed: (from, to) => holds.renamed(from, to)
8434
8485
  });
8435
8486
  const worker = opts.createWorker();
8436
- const dispatch2 = async (req) => {
8437
- if (isWasmSyscall(req)) return process.syscall(req);
8438
- return dispatchSyncFs(req);
8439
- };
8487
+ const dying = new Dying();
8488
+ const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
8440
8489
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
8441
- let settle3;
8442
- const exited = new Promise((resolve) => settle3 = resolve);
8490
+ const { promise: exited, resolve: settle3 } = Promise.withResolvers();
8443
8491
  let done4 = false;
8444
8492
  let endedBy;
8445
8493
  const finish = (code, sig) => {
8446
8494
  if (done4) return;
8447
8495
  done4 = true;
8496
+ dying.clear();
8448
8497
  endedBy = sig ?? process.execTermsig;
8449
8498
  worker.removeEventListener("message", onMessage);
8450
8499
  worker.removeEventListener("error", onError);
@@ -8460,10 +8509,10 @@ function spawnWasmProcess(opts) {
8460
8509
  };
8461
8510
  const handle = (data) => {
8462
8511
  if (data?.type === WASM_PROCESS_EXIT) {
8463
- finish(typeof data.code === "number" ? data.code : CRASHED);
8512
+ finish(...dying.status(data.code));
8464
8513
  } else if (data?.type === WASM_PROCESS_ERROR) {
8465
- opts.onError?.(String(data.message));
8466
- finish(CRASHED);
8514
+ if (!dying.ending) opts.onError?.(String(data.message));
8515
+ finish(...dying.status(CRASHED));
8467
8516
  } else if (data?.type === WASM_THREAD_SPAWN && data.thread) {
8468
8517
  spawnThread(data.thread);
8469
8518
  }
@@ -8533,7 +8582,10 @@ function spawnWasmProcess(opts) {
8533
8582
  };
8534
8583
  worker.postMessage(init, opts.fork ? [opts.fork.memory.buffer] : []);
8535
8584
  const signal = (sig) => {
8536
- if (process.signal(sig) === "terminate") finish(sig === SIG.KILL ? 137 : 128 + sig, sig);
8585
+ if (process.signal(sig) !== "terminate") return;
8586
+ const code = sig === SIG.KILL ? 137 : 128 + sig;
8587
+ if (opts.program.abi !== "wasi" || done4) finish(code, sig);
8588
+ else dying.start(code, sig, header2, process, () => finish(code, sig));
8537
8589
  };
8538
8590
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
8539
8591
  return {
@@ -8544,7 +8596,8 @@ function spawnWasmProcess(opts) {
8544
8596
  termsig: () => endedBy,
8545
8597
  onState: (listener) => process.onState(listener),
8546
8598
  memory: () => Atomics.load(header2, SAB_I_MEMORY) * 65536,
8547
- ignoredSignals: (fork) => process.inheritable(fork)
8599
+ ignoredSignals: (fork) => process.inheritable(fork),
8600
+ writesBack: () => opts.program.abi === "wasi" && !done4
8548
8601
  };
8549
8602
  }
8550
8603
 
@@ -10273,6 +10326,7 @@ var Launcher = class {
10273
10326
  spawner: this.spawner(pid, req.report),
10274
10327
  forker: this.forker(pid, req),
10275
10328
  kill: (target, sig) => this.kill(target, sig),
10329
+ writesBack: (target) => this.processes.get(target)?.writesBack() === true,
10276
10330
  processes: () => ({ boot: this.boot, processes: this.list() }),
10277
10331
  openFiles: this.openFiles,
10278
10332
  nodes: this.nodes,