@ai-ecoverse/slicc-kernel 1.122.3 → 1.123.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
@@ -918,6 +918,15 @@ Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `
918
918
 
919
919
  The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
920
920
 
921
+ ### How many processes at once
922
+
923
+ In a browser every process worker of the page shares one renderer. V8 reserves about 8 GiB of address space for each 32-bit wasm memory, whatever its declared maximum, and its sandbox holds about 1 TiB. So a page runs about 125 wasm processes (or programs that own a wasm memory) at once, counting everything in the renderer (#970). Node has no such ceiling, and neither does Linux. The kernel keeps processes inside that ceiling:
924
+ - **The ceiling:** 112 by default in a browser and none in Node. It's the kernel's count of live wasm processes, with the kernel's other wasm memories in the page left outside it. When an instantiation runs out of memory anyway, the kernel learns the real ceiling at that moment (the live count minus one), and that learned value wins over the default.
925
+ - **Admission:** a spawn, fork, exec or `kernel.run` at the ceiling waits for another process to exit. It keeps waiting as long as processes exit or are admitted, and fails with `EAGAIN` only after 10 s with neither. A long `make -j` or `xargs -P` therefore queues, and a pipeline that needs more live processes than the ceiling fails cleanly. bash's own retry on a fork's `EAGAIN` (`fork: retry: Resource temporarily unavailable`) handles the rest.
926
+ - **Close to the ceiling** (within 16 of it), the kernel waits for each new process to finish instantiating before the spawn returns. If it ran out of memory, the kernel starts it again, quietly, once there's room, so the caller sees no failure.
927
+ - **The rare residual case:** a process that fails to instantiate with nobody waiting on it, which happens only when something outside the count fills the renderer. It ends with status 126 and `<program>: cannot allocate memory for a new process (too many processes at once)` on its stderr, never a JS `RangeError`.
928
+ - **`/proc/sys/kernel/slicc_wasm_memories`** reads `<live> <ceiling>` (the ceiling 0 for none).
929
+
921
930
  ### Metadata
922
931
 
923
932
  OPFS stores names, bytes, sizes and modification times, nothing else. Everything POSIX needs on top of that lives in an IndexedDB sidecar, so it survives reloads and new kernels:
@@ -6408,7 +6408,7 @@ function satisfies(version, range2) {
6408
6408
  }
6409
6409
 
6410
6410
  // src/version.ts
6411
- var KERNEL_VERSION = true ? "1.122.3" : "0.0.0-dev";
6411
+ var KERNEL_VERSION = true ? "1.123.0" : "0.0.0-dev";
6412
6412
  var OS_RELEASE = "6.0.0-slicc";
6413
6413
  function procVersion() {
6414
6414
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -12551,17 +12551,28 @@ var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
12551
12551
  var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
12552
12552
  var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime", "version"];
12553
12553
  var SYS_TREE = {
12554
- kernel: { osrelease: `${OS_RELEASE}
12555
- `, ostype: "Linux\n" }
12554
+ kernel: {
12555
+ osrelease: `${OS_RELEASE}
12556
+ `,
12557
+ ostype: "Linux\n",
12558
+ slicc_wasm_memories: (listing) => `${listing.wasm?.live ?? 0} ${listing.wasm?.ceiling ?? 0}
12559
+ `
12560
+ }
12556
12561
  };
12562
+ function isSysFile(at) {
12563
+ return typeof at === "string" || typeof at === "function";
12564
+ }
12557
12565
  function sysEntry(parts2) {
12558
12566
  let at = SYS_TREE;
12559
12567
  for (const part of parts2) {
12560
- if (typeof at !== "object" || !Object.hasOwn(at, part)) return void 0;
12568
+ if (isSysFile(at) || !Object.hasOwn(at, part)) return void 0;
12561
12569
  at = at[part];
12562
12570
  }
12563
12571
  return at;
12564
12572
  }
12573
+ function sysText(file, listing) {
12574
+ return typeof file === "string" ? file : file(listing());
12575
+ }
12565
12576
  function commOf(info) {
12566
12577
  const name2 = info.argv[0] ?? "";
12567
12578
  return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
@@ -12797,7 +12808,9 @@ var ProcTree = class {
12797
12808
  }
12798
12809
  if (top === "sys") {
12799
12810
  const entry = sysEntry(parts2.slice(1));
12800
- if (typeof entry === "string") return { kind: "file", content: () => entry };
12811
+ if (typeof entry === "string" || typeof entry === "function") {
12812
+ return { kind: "file", content: () => sysText(entry, () => this.listing()) };
12813
+ }
12801
12814
  return entry && { kind: "dir", names: () => Object.keys(entry) };
12802
12815
  }
12803
12816
  if (top === "self") {
@@ -19254,6 +19267,7 @@ var WasmProcess = class {
19254
19267
  var WASM_PROCESS_INIT = "wasm-process-init";
19255
19268
  var WASM_PROCESS_EXIT = "wasm-process-exit";
19256
19269
  var WASM_PROCESS_ERROR = "wasm-process-error";
19270
+ var WASM_PROCESS_READY = "wasm-process-ready";
19257
19271
  var WASM_THREAD_SPAWN = "wasm-thread-spawn";
19258
19272
  var WASM_THREAD_INIT = "wasm-thread-init";
19259
19273
  var WASM_THREAD_EXIT = "wasm-thread-exit";
@@ -19261,6 +19275,8 @@ var WASM_MAX_THREADS = 256;
19261
19275
 
19262
19276
  // src/kernel/host.ts
19263
19277
  var CRASHED = 70;
19278
+ var NO_MEMORY = 126;
19279
+ var WASM_OOM = /Cannot allocate Wasm memory|WebAssembly\.Memory\(\): could not allocate memory/;
19264
19280
  var KILL_GRACE_MS = 2e3;
19265
19281
  var KILL_HARD_MS = 5e3;
19266
19282
  var descIds = /* @__PURE__ */ new WeakMap();
@@ -19526,7 +19542,22 @@ function threadInit(opts, program, sab, cred, thread, main) {
19526
19542
  function settleExit(exit, settle3, code) {
19527
19543
  void exit.catch(() => void 0).then(() => settle3(code & 255));
19528
19544
  }
19545
+ function readiness(abi) {
19546
+ const ready2 = Promise.withResolvers();
19547
+ if (abi === "js") ready2.resolve(true);
19548
+ return ready2;
19549
+ }
19550
+ function outOfMemory(opts, data) {
19551
+ if (data?.type !== WASM_PROCESS_ERROR || !WASM_OOM.test(String(data.message))) return false;
19552
+ if (!opts.onOom?.()) {
19553
+ opts.onError?.(
19554
+ `${opts.argv0}: cannot allocate memory for a new process (too many processes at once)`
19555
+ );
19556
+ }
19557
+ return true;
19558
+ }
19529
19559
  function spawnWasmProcess(opts) {
19560
+ const { promise: ready2, resolve: started } = readiness(opts.program.abi);
19530
19561
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
19531
19562
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
19532
19563
  const blocked2 = opts.fork?.wasi?.blocked ?? opts.blocked;
@@ -19579,6 +19610,7 @@ function spawnWasmProcess(opts) {
19579
19610
  const finish = (code, sig) => {
19580
19611
  if (done4) return;
19581
19612
  done4 = true;
19613
+ started(true);
19582
19614
  clocks.freeze();
19583
19615
  dying.clear();
19584
19616
  endedBy = sig ?? process.execTermsig;
@@ -19596,7 +19628,12 @@ function spawnWasmProcess(opts) {
19596
19628
  queueMicrotask(() => finish(...dying.status(code)));
19597
19629
  };
19598
19630
  const handle = (data) => {
19599
- if (data?.type === WASM_PROCESS_EXIT) {
19631
+ if (data?.type === WASM_PROCESS_READY) {
19632
+ started(true);
19633
+ } else if (outOfMemory(opts, data)) {
19634
+ started(false);
19635
+ finish(NO_MEMORY);
19636
+ } else if (data?.type === WASM_PROCESS_EXIT) {
19600
19637
  finish(...dying.status(data.code));
19601
19638
  } else if (data?.type === WASM_PROCESS_ERROR) {
19602
19639
  if (!dying.ending) opts.onError?.(String(data.message));
@@ -19670,6 +19707,7 @@ function spawnWasmProcess(opts) {
19670
19707
  return {
19671
19708
  pid: opts.pid,
19672
19709
  exited,
19710
+ ready: ready2,
19673
19711
  kill,
19674
19712
  signal,
19675
19713
  termsig: () => endedBy,
@@ -21264,6 +21302,63 @@ var SettlingChildren = class {
21264
21302
  }
21265
21303
  };
21266
21304
 
21305
+ // src/kernel/wasm-memories.ts
21306
+ var DEFAULT_CEILING = 112;
21307
+ var IDLE_MS = 1e4;
21308
+ var MARGIN = 16;
21309
+ var WasmMemories = class {
21310
+ live = 0;
21311
+ ceiling;
21312
+ waiters = /* @__PURE__ */ new Set();
21313
+ idleMs;
21314
+ constructor(ceiling, idleMs = IDLE_MS) {
21315
+ this.ceiling = ceiling;
21316
+ this.idleMs = idleMs;
21317
+ }
21318
+ count() {
21319
+ return { live: this.live, ceiling: this.ceiling ?? 0 };
21320
+ }
21321
+ ended() {
21322
+ this.live = Math.max(0, this.live - 1);
21323
+ this.progress();
21324
+ }
21325
+ learn() {
21326
+ this.ceiling = Math.max(1, this.live - 1);
21327
+ }
21328
+ near() {
21329
+ return this.ceiling !== void 0 && this.live + MARGIN >= this.ceiling;
21330
+ }
21331
+ async admit() {
21332
+ while (this.ceiling !== void 0 && this.live >= this.ceiling) {
21333
+ if (!await this.changed()) throw new SpawnError("EAGAIN");
21334
+ }
21335
+ this.live += 1;
21336
+ this.progress();
21337
+ }
21338
+ progress() {
21339
+ const waiters = [...this.waiters];
21340
+ this.waiters.clear();
21341
+ for (const wake of waiters) wake();
21342
+ }
21343
+ changed() {
21344
+ return new Promise((resolve2) => {
21345
+ const wake = () => {
21346
+ clearTimeout(timer);
21347
+ resolve2(true);
21348
+ };
21349
+ const timer = setTimeout(() => {
21350
+ this.waiters.delete(wake);
21351
+ resolve2(false);
21352
+ }, this.idleMs);
21353
+ this.waiters.add(wake);
21354
+ });
21355
+ }
21356
+ };
21357
+ function defaultCeiling() {
21358
+ const node = globalThis.process?.versions?.node;
21359
+ return node === void 0 ? DEFAULT_CEILING : void 0;
21360
+ }
21361
+
21267
21362
  // src/mount/driver.ts
21268
21363
  function ownerOf2(req) {
21269
21364
  const owner = {
@@ -23461,6 +23556,9 @@ function withScriptEnv(target, command) {
23461
23556
  ]
23462
23557
  };
23463
23558
  }
23559
+ function memoriesOf(given) {
23560
+ return new WasmMemories(given?.ceiling ?? defaultCeiling(), given?.idleMs);
23561
+ }
23464
23562
  function childHandle(handle) {
23465
23563
  return {
23466
23564
  pid: handle.pid,
@@ -23532,6 +23630,7 @@ var Launcher = class {
23532
23630
  jobs = new JobTable();
23533
23631
  locks;
23534
23632
  fifos = new FifoTable();
23633
+ memories;
23535
23634
  sems = new SemTable();
23536
23635
  settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
23537
23636
  ptys = new PtyTable(
@@ -23585,6 +23684,7 @@ var Launcher = class {
23585
23684
  terminals = 0;
23586
23685
  constructor(options) {
23587
23686
  this.usersMode = usersMode(options.users);
23687
+ this.memories = memoriesOf(options.wasmMemories);
23588
23688
  this.rootFs = options.fs;
23589
23689
  const peers = options.fs.peers?.();
23590
23690
  this.locks = kernelLocks(options.fs.rootName, peers);
@@ -24415,7 +24515,11 @@ var Launcher = class {
24415
24515
  }
24416
24516
  const done4 = this.heldUrl(program);
24417
24517
  try {
24418
- const handle = this.start({ ...req, cwd, program, argv0: target.argv0, args, env });
24518
+ const handle = await this.admitted(
24519
+ program.abi !== "js",
24520
+ req.fds,
24521
+ (fds, quietOom) => this.start({ ...req, fds, quietOom, cwd, program, argv0: target.argv0, args, env })
24522
+ );
24419
24523
  void handle.exited.finally(done4);
24420
24524
  return handle;
24421
24525
  } catch (err) {
@@ -24459,6 +24563,39 @@ var Launcher = class {
24459
24563
  if (req.tag !== void 0) this.tags.set(pid, req.tag);
24460
24564
  if (req.links !== void 0) this.links.set(pid, req.links);
24461
24565
  }
24566
+ async admitted(wasm, fds, start2) {
24567
+ if (!wasm) return start2(fds);
24568
+ for (; ; ) {
24569
+ await this.memories.admit().catch(async (err) => {
24570
+ await fds.closeAll();
24571
+ throw err;
24572
+ });
24573
+ const counted2 = (handle2) => {
24574
+ void handle2.exited.finally(() => this.memories.ended());
24575
+ return handle2;
24576
+ };
24577
+ if (!this.memories.near()) {
24578
+ try {
24579
+ return counted2(start2(fds));
24580
+ } catch (err) {
24581
+ this.memories.ended();
24582
+ throw err;
24583
+ }
24584
+ }
24585
+ const forked = fds.fork();
24586
+ const handle = await Promise.resolve().then(() => counted2(start2(forked, true))).catch(async (err) => {
24587
+ this.memories.ended();
24588
+ await forked.closeAll();
24589
+ throw err;
24590
+ });
24591
+ const fit = await (handle.ready ?? true);
24592
+ if (fit) {
24593
+ await fds.closeAll();
24594
+ return handle;
24595
+ }
24596
+ await handle.exited;
24597
+ }
24598
+ }
24462
24599
  start(req) {
24463
24600
  this.sampling();
24464
24601
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
@@ -24509,6 +24646,10 @@ var Launcher = class {
24509
24646
  ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
24510
24647
  createWorker: this.createWorker,
24511
24648
  onError: req.report,
24649
+ onOom: () => {
24650
+ this.memories.learn();
24651
+ return req.quietOom === true;
24652
+ },
24512
24653
  spawner: this.spawner(pid, req.report),
24513
24654
  forker: this.forker(pid, req),
24514
24655
  kill: (target, sig, info) => this.kill(target, sig, this.credOf(pid), pid, info),
@@ -24517,7 +24658,8 @@ var Launcher = class {
24517
24658
  boot: this.boot,
24518
24659
  processes: this.list(this.credOf(pid)),
24519
24660
  load: this.loadAverage(),
24520
- cpu: this.cpuTotal()
24661
+ cpu: this.cpuTotal(),
24662
+ wasm: this.memories.count()
24521
24663
  }),
24522
24664
  sudo: (call, signal) => this.sudo(pid, call, signal),
24523
24665
  crontab: (call) => this.crontab(pid, call),
@@ -24854,9 +24996,10 @@ var Launcher = class {
24854
24996
  }
24855
24997
  forker(ppid, parent) {
24856
24998
  const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
24857
- return async (state, fds) => childHandle(
24858
- this.start({
24999
+ return async (state, given) => {
25000
+ const fresh2 = (fds, quietOom) => this.start({
24859
25001
  ...image,
25002
+ quietOom,
24860
25003
  cwd: state.cwd ?? parent.cwd,
24861
25004
  fds,
24862
25005
  ppid,
@@ -24867,8 +25010,9 @@ var Launcher = class {
24867
25010
  cred: this.credOf(ppid),
24868
25011
  tag: this.tagOf(ppid),
24869
25012
  links: this.links.get(ppid)
24870
- })
24871
- );
25013
+ });
25014
+ return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh2));
25015
+ };
24872
25016
  }
24873
25017
  keySignal(tty, fallback, sig, send) {
24874
25018
  const pgid = this.jobs.tcgetpgrp(tty, fallback);