@ai-ecoverse/slicc-kernel 1.122.2 → 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 +10 -0
- package/dist/kernel-worker.js +159 -13
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +74 -8
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +159 -13
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +74 -8
- package/dist/process-worker.js.map +3 -3
- package/dist/types/kernel/host.d.ts +2 -0
- package/dist/types/kernel/proc-info.d.ts +4 -0
- package/dist/types/kernel/protocol.d.ts +1 -0
- package/dist/types/kernel/wasm-memories.d.ts +20 -0
- package/dist/types/launcher.d.ts +6 -0
- package/dist/types/process/procfs.d.ts +4 -2
- package/dist/types/realm/live-vfs-fs.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -611,6 +611,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
611
611
|
- **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
|
|
612
612
|
- **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. An `fsa` mount (a picked folder) has no sync access handles, and Chrome copies the whole file into a swap file for every writable stream, so it writes through one stream per open file: the first write back opens it, later runs go into it at their offsets, and it is committed, one copy of the file, when the last process writing the file closes it (or exits), before another process reads what was written, and before a rename, unlink or unmount. Until then other processes see the new size but read the old bytes outside what was written. Elsewhere (`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. Before a program's next call into the kernel (any wait, read of a pipe or terminal, sleep or spawn), its changed pages also go back, so other processes and the embedder see a write as soon as the writer does anything but compute: a daemon's pidfile, a log another process follows. On a whole-file mount that holds only for files of up to 64 KiB; a larger one goes back at close, fsync and exit, as before. Beyond that, 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.
|
|
613
613
|
- **Exclusive create across tabs** ([#912](https://github.com/ai-ecoverse/slicc-kernel/issues/912)): two kernels on one OPFS root (two tabs, or a tab and a worker page) agree on `O_CREAT|O_EXCL` and `set -C`: exactly one wins a name. While another kernel on the root is present, the create runs under a Web Lock for its path and checks the OPFS entry itself. A kernel alone on its root takes no lock. Each kernel holds an alive Web Lock, granted before it first looks for peers. A kernel that finds peers waits for their answer (at most 500 ms) before its first exclusive create, and a kernel answers a newcomer only once its unlocked creates are done. So of two kernels that boot at the same moment, the later one to look sees the other, and no unlocked create overlaps a locked one.
|
|
614
|
+
- **A name another process or tab just removed** ([#950](https://github.com/ai-ecoverse/slicc-kernel/issues/950)): sqlite3 deletes its `-journal` at every commit and the next writer creates it again, so two writers (two processes in one kernel, or two tabs on one OPFS root) keep removing and re-creating one name. Two things used to turn that into `unable to open database file`. The root removes a file's sidecar record a few milliseconds after the file itself (removals are batched), and an exclusive create read that pending record as the name being taken: a plain record whose file is gone now counts as gone, and the new file gets a record of its own. A symlink, which lives only in the sidecar, still takes its name. And an Emscripten program keeps the nodes it has looked up, so after another tab removed a name it still held the old node, and `open(O_CREAT)` opened the gone file instead of creating one. Now, when the kernel answers `ENOENT` for a cached node, for `open`, `stat`, `lstat`, `readlink`, `truncate`, `unlink` or a rename's source, or the program's own lookup answers `EEXIST` to `open(O_CREAT|O_EXCL)`, the node is dropped and the call runs once more, against what is there now. A node the program has open or has unwritten changes in is kept. A rename over a name another tab removed needs nothing: the kernel's rename replaces whatever is there.
|
|
614
615
|
- **One file, several processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): a process that keeps a file open sees what another process writes to it. Every change the kernel applies to a file (a write back, a truncate, an append, a rename or a removal) bumps a change ring the kernel shares with all its processes. Before each read, write or seek to the end, a process's open file checks the ring (one atomic load) and, if its file changed, drops its clean pages and reads the size again, keeping its own unwritten changes. A write by one process is visible to another one's next read once it has gone back to the kernel: at the latest at the writer's next `fsync`, close, exit or call into the kernel (see above), so sqlite, which `fsync`s every commit, sees another connection's commit at its next transaction. A write through a file the kernel holds (a JS program's `ctx.open`, a WASI program's file after a fork or spawn) is in the kernel already, so other processes see it at their next read, before it is written back. A file the kernel itself holds open (a JS program's file, a WASI program's file after a fork or spawn) is the one copy: reads, writes, truncates and appends by path from other processes go to it, and their writes go through to storage at once, so a commit is in storage (and in `kernel.readFile`) when the committing process returns from `fsync`. `O_APPEND` writes go to the kernel at once, and the kernel picks the offset, so appenders in several processes keep every line. A file system without ranged I/O (`hostfs` with an older proxy, a root outside OPFS) rereads a file another process changed, but writes it back whole, so two writers there still need locks. The same file under two names is one file too ([#669](https://github.com/ai-ecoverse/slicc-kernel/issues/669)): a write by path through a symlink reaches the file the kernel holds under its real path (resolved without following links in a `sudo` process's no-links view, so an untrusted link is still refused), a WASI program's open file also watches its real path (resolved once, the first time another file changes), and an open file with more than one hard link checks its size, version and mtime when anything changed, so a write through another name is seen. Limits for now: an alias on a mount that has no `realpath` is still only the name it was opened by; another tab's writes reach this tab's processes only as far as the root's change notifications (OPFS's `BroadcastChannel` watch) carry them. A by-path `stat` or `lstat` of a file the kernel holds open has its current size, writes not yet written back included ([#780](https://github.com/ai-ecoverse/slicc-kernel/issues/780)). Its mtime is the file system's until write-back, though, so a tool that compares times, like `make`, may see a just-written file as older than it is for up to the write-back delay.
|
|
615
616
|
- **Locks between processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): an Emscripten program's `fcntl` byte-range locks (`F_SETLK`, `F_SETLKW`, `F_GETLK`) are the kernel's, and so are a WASIX program's through the `slicc.fcntl_lock` import (below), so sqlite3 connections in different processes exclude each other: a second writer gets `database is locked` while the first holds its transaction, or waits with a busy timeout. The kernel keeps one table keyed by file identity (device and inode, so every name of a hard link is the same file), with POSIX semantics: locks belong to the process (an `exec`'d program keeps them under its pid; a forked child doesn't get them), a conflicting `F_SETLK` fails with `EAGAIN`, `F_GETLK` reports the holder's pid and range, `F_SETLKW` waits in the kernel without holding up anything else and a signal ends the wait with `EINTR`, a wait that would close a cycle between waiting processes fails with `EDEADLK`, a write lock on a descriptor not open for writing (or a read lock on one not open for reading) is `EBADF`, closing any descriptor of the file drops the process's locks on it, and exit releases them all. Before an unlock, the process writes back what it changed, so the next holder reads it. An open file's directory entry is checked against the change ring too: a name another process removed or replaced is looked up again, so sqlite's check for a hot journal sees the other connection's commit. A WASI imports module's `tryLock`/`unlock` keeps its behaviour (whole file, per process, keyed by path). Limits: locks are per kernel, not across tabs ([#675](https://github.com/ai-ecoverse/slicc-kernel/issues/675)); and a WASIX program built against a wasix-sysroot without the import keeps its libc's local answer (every lock granted), so python's sqlite excludes other processes only once wasix-python is rebuilt on the sysroot that has it ([homescoop#407](https://github.com/ai-ecoverse/homescoop/issues/407)). An Emscripten program's `flock()` reaches the kernel's `flock` table (per open file description) through homescoop's shim, a strong `flock()` that calls the import `env.__syscall_flock(fd, op)`. The kernel wraps that import by name (#947): `LOCK_SH`/`LOCK_EX`/`LOCK_UN` with `LOCK_NB` (`EWOULDBLOCK` for a conflict), 0 or -errno. A glue without the shim keeps Emscripten's stub, which answers 0. A lock belongs to the file its descriptor was opened on: a file renamed over the name later, or the name's removal, doesn't move it.
|
|
616
617
|
- **Locks across tabs** ([#675](https://github.com/ai-ecoverse/slicc-kernel/issues/675)): two kernels on one OPFS root (two tabs, or a tab and a worker page) share one table of `fcntl` record locks and `flock` locks, so a sqlite3 in one tab finds the database locked while a transaction is open in another. **Known issue, data corruption: lock-protected writes across tabs** ([#949](https://github.com/ai-ecoverse/slicc-kernel/issues/949)). The locks are enforced across tabs, but what one tab writes under a lock isn't yet guaranteed to be what the other tab reads once it holds the lock, so a lock-protected read-modify-write across tabs can lose or corrupt data. Don't rely on cross-tab locking until #949 is fixed. Use within a single tab is unaffected.
|
|
@@ -917,6 +918,15 @@ Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `
|
|
|
917
918
|
|
|
918
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.
|
|
919
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
|
+
|
|
920
930
|
### Metadata
|
|
921
931
|
|
|
922
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:
|
package/dist/kernel-worker.js
CHANGED
|
@@ -2051,6 +2051,7 @@ var OpfsFs = class {
|
|
|
2051
2051
|
if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
|
|
2052
2052
|
const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
|
|
2053
2053
|
if (freshMeta[0]?.link !== void 0) throw fsError("EEXIST", path);
|
|
2054
|
+
if (freshMeta[0] !== void 0) await this.meta.remove([path]);
|
|
2054
2055
|
this.forget(physical);
|
|
2055
2056
|
this.refuse(physical);
|
|
2056
2057
|
await dir.getDirectoryHandle(name2, { create: true });
|
|
@@ -2070,7 +2071,8 @@ var OpfsFs = class {
|
|
|
2070
2071
|
await this.locked(physical, async () => {
|
|
2071
2072
|
if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
|
|
2072
2073
|
const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
|
|
2073
|
-
if (freshMeta[0] !== void 0) throw fsError("EEXIST", path);
|
|
2074
|
+
if (freshMeta[0]?.link !== void 0) throw fsError("EEXIST", path);
|
|
2075
|
+
if (freshMeta[0] !== void 0) await this.meta.remove([path]);
|
|
2074
2076
|
this.forget(physical);
|
|
2075
2077
|
this.refuse(physical);
|
|
2076
2078
|
await dir.getFileHandle(name2, { create: true });
|
|
@@ -6406,7 +6408,7 @@ function satisfies(version, range2) {
|
|
|
6406
6408
|
}
|
|
6407
6409
|
|
|
6408
6410
|
// src/version.ts
|
|
6409
|
-
var KERNEL_VERSION = true ? "1.
|
|
6411
|
+
var KERNEL_VERSION = true ? "1.123.0" : "0.0.0-dev";
|
|
6410
6412
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6411
6413
|
function procVersion() {
|
|
6412
6414
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -12549,17 +12551,28 @@ var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
|
|
|
12549
12551
|
var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
|
|
12550
12552
|
var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime", "version"];
|
|
12551
12553
|
var SYS_TREE = {
|
|
12552
|
-
kernel: {
|
|
12553
|
-
|
|
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
|
+
}
|
|
12554
12561
|
};
|
|
12562
|
+
function isSysFile(at) {
|
|
12563
|
+
return typeof at === "string" || typeof at === "function";
|
|
12564
|
+
}
|
|
12555
12565
|
function sysEntry(parts2) {
|
|
12556
12566
|
let at = SYS_TREE;
|
|
12557
12567
|
for (const part of parts2) {
|
|
12558
|
-
if (
|
|
12568
|
+
if (isSysFile(at) || !Object.hasOwn(at, part)) return void 0;
|
|
12559
12569
|
at = at[part];
|
|
12560
12570
|
}
|
|
12561
12571
|
return at;
|
|
12562
12572
|
}
|
|
12573
|
+
function sysText(file, listing) {
|
|
12574
|
+
return typeof file === "string" ? file : file(listing());
|
|
12575
|
+
}
|
|
12563
12576
|
function commOf(info) {
|
|
12564
12577
|
const name2 = info.argv[0] ?? "";
|
|
12565
12578
|
return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
|
|
@@ -12795,7 +12808,9 @@ var ProcTree = class {
|
|
|
12795
12808
|
}
|
|
12796
12809
|
if (top === "sys") {
|
|
12797
12810
|
const entry = sysEntry(parts2.slice(1));
|
|
12798
|
-
if (typeof entry === "string"
|
|
12811
|
+
if (typeof entry === "string" || typeof entry === "function") {
|
|
12812
|
+
return { kind: "file", content: () => sysText(entry, () => this.listing()) };
|
|
12813
|
+
}
|
|
12799
12814
|
return entry && { kind: "dir", names: () => Object.keys(entry) };
|
|
12800
12815
|
}
|
|
12801
12816
|
if (top === "self") {
|
|
@@ -19252,6 +19267,7 @@ var WasmProcess = class {
|
|
|
19252
19267
|
var WASM_PROCESS_INIT = "wasm-process-init";
|
|
19253
19268
|
var WASM_PROCESS_EXIT = "wasm-process-exit";
|
|
19254
19269
|
var WASM_PROCESS_ERROR = "wasm-process-error";
|
|
19270
|
+
var WASM_PROCESS_READY = "wasm-process-ready";
|
|
19255
19271
|
var WASM_THREAD_SPAWN = "wasm-thread-spawn";
|
|
19256
19272
|
var WASM_THREAD_INIT = "wasm-thread-init";
|
|
19257
19273
|
var WASM_THREAD_EXIT = "wasm-thread-exit";
|
|
@@ -19259,6 +19275,8 @@ var WASM_MAX_THREADS = 256;
|
|
|
19259
19275
|
|
|
19260
19276
|
// src/kernel/host.ts
|
|
19261
19277
|
var CRASHED = 70;
|
|
19278
|
+
var NO_MEMORY = 126;
|
|
19279
|
+
var WASM_OOM = /Cannot allocate Wasm memory|WebAssembly\.Memory\(\): could not allocate memory/;
|
|
19262
19280
|
var KILL_GRACE_MS = 2e3;
|
|
19263
19281
|
var KILL_HARD_MS = 5e3;
|
|
19264
19282
|
var descIds = /* @__PURE__ */ new WeakMap();
|
|
@@ -19524,7 +19542,22 @@ function threadInit(opts, program, sab, cred, thread, main) {
|
|
|
19524
19542
|
function settleExit(exit, settle3, code) {
|
|
19525
19543
|
void exit.catch(() => void 0).then(() => settle3(code & 255));
|
|
19526
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
|
+
}
|
|
19527
19559
|
function spawnWasmProcess(opts) {
|
|
19560
|
+
const { promise: ready2, resolve: started } = readiness(opts.program.abi);
|
|
19528
19561
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
19529
19562
|
const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
19530
19563
|
const blocked2 = opts.fork?.wasi?.blocked ?? opts.blocked;
|
|
@@ -19577,6 +19610,7 @@ function spawnWasmProcess(opts) {
|
|
|
19577
19610
|
const finish = (code, sig) => {
|
|
19578
19611
|
if (done4) return;
|
|
19579
19612
|
done4 = true;
|
|
19613
|
+
started(true);
|
|
19580
19614
|
clocks.freeze();
|
|
19581
19615
|
dying.clear();
|
|
19582
19616
|
endedBy = sig ?? process.execTermsig;
|
|
@@ -19594,7 +19628,12 @@ function spawnWasmProcess(opts) {
|
|
|
19594
19628
|
queueMicrotask(() => finish(...dying.status(code)));
|
|
19595
19629
|
};
|
|
19596
19630
|
const handle = (data) => {
|
|
19597
|
-
if (data?.type ===
|
|
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) {
|
|
19598
19637
|
finish(...dying.status(data.code));
|
|
19599
19638
|
} else if (data?.type === WASM_PROCESS_ERROR) {
|
|
19600
19639
|
if (!dying.ending) opts.onError?.(String(data.message));
|
|
@@ -19668,6 +19707,7 @@ function spawnWasmProcess(opts) {
|
|
|
19668
19707
|
return {
|
|
19669
19708
|
pid: opts.pid,
|
|
19670
19709
|
exited,
|
|
19710
|
+
ready: ready2,
|
|
19671
19711
|
kill,
|
|
19672
19712
|
signal,
|
|
19673
19713
|
termsig: () => endedBy,
|
|
@@ -21262,6 +21302,63 @@ var SettlingChildren = class {
|
|
|
21262
21302
|
}
|
|
21263
21303
|
};
|
|
21264
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
|
+
|
|
21265
21362
|
// src/mount/driver.ts
|
|
21266
21363
|
function ownerOf2(req) {
|
|
21267
21364
|
const owner = {
|
|
@@ -23459,6 +23556,9 @@ function withScriptEnv(target, command) {
|
|
|
23459
23556
|
]
|
|
23460
23557
|
};
|
|
23461
23558
|
}
|
|
23559
|
+
function memoriesOf(given) {
|
|
23560
|
+
return new WasmMemories(given?.ceiling ?? defaultCeiling(), given?.idleMs);
|
|
23561
|
+
}
|
|
23462
23562
|
function childHandle(handle) {
|
|
23463
23563
|
return {
|
|
23464
23564
|
pid: handle.pid,
|
|
@@ -23530,6 +23630,7 @@ var Launcher = class {
|
|
|
23530
23630
|
jobs = new JobTable();
|
|
23531
23631
|
locks;
|
|
23532
23632
|
fifos = new FifoTable();
|
|
23633
|
+
memories;
|
|
23533
23634
|
sems = new SemTable();
|
|
23534
23635
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
23535
23636
|
ptys = new PtyTable(
|
|
@@ -23583,6 +23684,7 @@ var Launcher = class {
|
|
|
23583
23684
|
terminals = 0;
|
|
23584
23685
|
constructor(options) {
|
|
23585
23686
|
this.usersMode = usersMode(options.users);
|
|
23687
|
+
this.memories = memoriesOf(options.wasmMemories);
|
|
23586
23688
|
this.rootFs = options.fs;
|
|
23587
23689
|
const peers = options.fs.peers?.();
|
|
23588
23690
|
this.locks = kernelLocks(options.fs.rootName, peers);
|
|
@@ -24413,7 +24515,11 @@ var Launcher = class {
|
|
|
24413
24515
|
}
|
|
24414
24516
|
const done4 = this.heldUrl(program);
|
|
24415
24517
|
try {
|
|
24416
|
-
const handle = this.
|
|
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
|
+
);
|
|
24417
24523
|
void handle.exited.finally(done4);
|
|
24418
24524
|
return handle;
|
|
24419
24525
|
} catch (err) {
|
|
@@ -24457,6 +24563,39 @@ var Launcher = class {
|
|
|
24457
24563
|
if (req.tag !== void 0) this.tags.set(pid, req.tag);
|
|
24458
24564
|
if (req.links !== void 0) this.links.set(pid, req.links);
|
|
24459
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
|
+
}
|
|
24460
24599
|
start(req) {
|
|
24461
24600
|
this.sampling();
|
|
24462
24601
|
if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
|
|
@@ -24507,6 +24646,10 @@ var Launcher = class {
|
|
|
24507
24646
|
...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
|
|
24508
24647
|
createWorker: this.createWorker,
|
|
24509
24648
|
onError: req.report,
|
|
24649
|
+
onOom: () => {
|
|
24650
|
+
this.memories.learn();
|
|
24651
|
+
return req.quietOom === true;
|
|
24652
|
+
},
|
|
24510
24653
|
spawner: this.spawner(pid, req.report),
|
|
24511
24654
|
forker: this.forker(pid, req),
|
|
24512
24655
|
kill: (target, sig, info) => this.kill(target, sig, this.credOf(pid), pid, info),
|
|
@@ -24515,7 +24658,8 @@ var Launcher = class {
|
|
|
24515
24658
|
boot: this.boot,
|
|
24516
24659
|
processes: this.list(this.credOf(pid)),
|
|
24517
24660
|
load: this.loadAverage(),
|
|
24518
|
-
cpu: this.cpuTotal()
|
|
24661
|
+
cpu: this.cpuTotal(),
|
|
24662
|
+
wasm: this.memories.count()
|
|
24519
24663
|
}),
|
|
24520
24664
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
24521
24665
|
crontab: (call) => this.crontab(pid, call),
|
|
@@ -24852,9 +24996,10 @@ var Launcher = class {
|
|
|
24852
24996
|
}
|
|
24853
24997
|
forker(ppid, parent) {
|
|
24854
24998
|
const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
|
|
24855
|
-
return async (state,
|
|
24856
|
-
this.start({
|
|
24999
|
+
return async (state, given) => {
|
|
25000
|
+
const fresh2 = (fds, quietOom) => this.start({
|
|
24857
25001
|
...image,
|
|
25002
|
+
quietOom,
|
|
24858
25003
|
cwd: state.cwd ?? parent.cwd,
|
|
24859
25004
|
fds,
|
|
24860
25005
|
ppid,
|
|
@@ -24865,8 +25010,9 @@ var Launcher = class {
|
|
|
24865
25010
|
cred: this.credOf(ppid),
|
|
24866
25011
|
tag: this.tagOf(ppid),
|
|
24867
25012
|
links: this.links.get(ppid)
|
|
24868
|
-
})
|
|
24869
|
-
|
|
25013
|
+
});
|
|
25014
|
+
return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh2));
|
|
25015
|
+
};
|
|
24870
25016
|
}
|
|
24871
25017
|
keySignal(tty, fallback, sig, send) {
|
|
24872
25018
|
const pgid = this.jobs.tcgetpgrp(tty, fallback);
|