@ai-ecoverse/slicc-kernel 1.134.1 → 1.134.4
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 +5 -4
- package/dist/kernel-worker.js +30 -26
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +128 -64
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +31 -26
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +128 -64
- package/dist/process-worker.js.map +4 -4
- package/dist/types/fs/opfs.d.ts +0 -1
- package/dist/types/kernel/proc-info.d.ts +1 -0
- package/dist/types/kernel/wasm-memories.d.ts +1 -0
- package/dist/types/node.d.ts +4 -0
- package/dist/types/process/js/js-context.d.ts +2 -0
- package/dist/types/process/process-fds.d.ts +4 -0
- package/dist/types/realm/emscripten-held-cwd.d.ts +11 -0
- package/dist/types/realm/emscripten-vfs-hook.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -859,13 +859,14 @@ A process's working directory is the directory it changed into, not just its pat
|
|
|
859
859
|
- a wasip1 program's `.` (its `.` preopen) can't be opened or listed (`ENOENT`, an empty listing for its preopen descriptor);
|
|
860
860
|
- its relative lookups and creates are `ENOENT`;
|
|
861
861
|
- `..` still reaches the parent.
|
|
862
|
+
- an Emscripten program (bash, coreutils, …) and its forked subshells and children: relative paths resolve against `<path> (deleted)`, so `ls` fails with `cannot open directory '.'`, a glob matches nothing, and a relative create is `ENOENT`. `getcwd` is `ENOENT`, as on Linux, so a shell started there says `shell-init: error retrieving current directory` and its `pwd` fails, while bash's own `$PWD`, `cd ..` and `cd /abs` keep working.
|
|
863
|
+
- a JS program: `ctx.fs` and `ctx.sync.fs` with relative paths are `ENOENT`, and `../x` reaches the parent. `ctx.cwd` (and `process.cwd()`) keeps the plain path, as a WASIX program's `getcwd` does. This needs the sync lane, which JS programs have where `SharedArrayBuffer` is available.
|
|
862
864
|
- **Detection costs nothing in the steady state:**
|
|
863
865
|
- each held directory has a counter in memory the kernel shares with the process. It moves only when this kernel, another tab, or a `postFsNotice` reports a change at that directory or above it: the kernel's own watcher paths, and every path and tree of a peer change or notice.
|
|
864
866
|
- a process checks the counter, a memory read, before each relative lookup. Only after it moved does it ask the kernel once (`cwd-check`), and the kernel compares the inode at the path with the one it recorded.
|
|
865
867
|
- **The `rm` record:** so that `rm -r d; mkdir d` can be told apart, removing a directory leaves the same small record a rename leaves at the name it vacates (see [Metadata](#metadata)).
|
|
866
868
|
- **Limits:**
|
|
867
869
|
- a WASIX program's C library keeps the working directory as a string and turns relative paths into absolute ones before they reach the kernel. So a WASIX program still sees the new directory through relative paths, and `getcwd` still returns the path, until wasix-sysroot resolves relative paths against the working directory's descriptor ([#1164](https://github.com/ai-ecoverse/slicc-kernel/issues/1164) stays open for that).
|
|
868
|
-
- Emscripten and JS programs: [#1164](https://github.com/ai-ecoverse/slicc-kernel/issues/1164), next.
|
|
869
870
|
- a removal between the check and the lookup is a race, as with any path, and the lookup sees the new directory.
|
|
870
871
|
- on a `hostfs` folder the inode is the host's, which a host file system may reuse for the new directory (ext4 can; APFS doesn't). There the replacement isn't noticed.
|
|
871
872
|
- with more than 1023 distinct working directories held at once, the rest are not tracked.
|
|
@@ -1096,14 +1097,14 @@ The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#us
|
|
|
1096
1097
|
### How many processes at once
|
|
1097
1098
|
|
|
1098
1099
|
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:
|
|
1099
|
-
- **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. A learned ceiling is never below 16, since an OOM at a low count means something outside it (another tab in the same renderer) filled the renderer. It's raised by 8 for each minute without another OOM, back to the default.
|
|
1100
|
-
- **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
|
|
1100
|
+
- **The ceiling:** 112 by default in a browser and none in Node (`createNodeKernel({ wasmMemories: { ceiling, idleMs } })` sets one in Node; `idleMs`, the quiet spell before `EAGAIN` below, 10 s by default, is there for tests). 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. A learned ceiling is never below 16, since an OOM at a low count means something outside it (another tab in the same renderer) filled the renderer. It's raised by 8 for each minute without another OOM, back to the default.
|
|
1101
|
+
- **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, are admitted or finish starting (a process becoming ready counts, so a slow host that is still booting the processes ahead of it doesn't fail the spawn, #1196), and fails with `EAGAIN` only after 10 s with none of these. 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.
|
|
1101
1102
|
- **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.
|
|
1102
1103
|
- **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`.
|
|
1103
1104
|
- **Below a learned ceiling,** a process that still runs out of memory is started again once another process exits, and the spawn fails with `EAGAIN` after 10 s with none.
|
|
1104
1105
|
- **`/proc/sys/kernel/slicc_wasm_memories`** reads `<live> <ceiling> <source>`. The source is `default`, `learned`, or `none` (with the ceiling 0).
|
|
1105
1106
|
|
|
1106
|
-
`/proc/sys/kernel/slicc_shared_root` reads `1` while another kernel shares the root (a tab, or a second kernel in the page) and `0` otherwise. A peer whose kernel ends (its tab closes or reloads) stops counting at once: each kernel queues a request for every peer's alive Web Lock, and the grant tells it the peer has gone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_fs_calls` reads the number of path calls (lookups, stats, opens, creates, removals and the like) the kernel has served its programs since it started. `/proc/sys/kernel/slicc_opfs_calls` counts the OPFS calls the kernel has made to look names up and read stats (`getFile`, `getFileHandle`, `getDirectoryHandle`, `resolve`). `/proc/sys/kernel/slicc_opfs_ops` counts every request the root file system makes of OPFS: those lookups, plus creates, writes (one per write, truncate or write session), reads, listings, renames and removals ([#1017](https://github.com/ai-ecoverse/slicc-kernel/issues/1017)). With an idle peer, a tar unpack makes at most 1.5 times the calls it makes alone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)).
|
|
1107
|
+
`/proc/sys/kernel/slicc_shared_root` reads `1` while another kernel shares the root (a tab, or a second kernel in the page) and `0` otherwise. A peer whose kernel ends (its tab closes or reloads) stops counting at once: each kernel queues a request for every peer's alive Web Lock, and the grant tells it the peer has gone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_fs_calls` reads the number of path calls (lookups, stats, opens, creates, removals and the like) the kernel has served its programs since it started. `/proc/sys/kernel/slicc_opfs_calls` counts the OPFS calls the kernel has made to look names up and read stats (`getFile`, `getFileHandle`, `getDirectoryHandle`, `resolve`). `/proc/sys/kernel/slicc_opfs_ops` counts every request the root file system makes of OPFS: those lookups, plus creates, writes (one per write, truncate or write session), reads, listings, renames and removals ([#1017](https://github.com/ai-ecoverse/slicc-kernel/issues/1017)). With an idle peer, a tar unpack makes at most 1.5 times the calls it makes alone ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)). `/proc/sys/kernel/slicc_cwd_checks` counts the times a process asked the kernel whether its [working directory](#the-working-directory) is still the one it holds.
|
|
1107
1108
|
|
|
1108
1109
|
### Metadata
|
|
1109
1110
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -2378,14 +2378,14 @@ var OpfsFs = class {
|
|
|
2378
2378
|
const staged = resolvePath(parent.path, staging);
|
|
2379
2379
|
const place = async () => {
|
|
2380
2380
|
this.forget(destination);
|
|
2381
|
-
const
|
|
2381
|
+
const copies = /* @__PURE__ */ new Map();
|
|
2382
2382
|
const root = { ino: inodeOf(source.path), ...moving };
|
|
2383
2383
|
const owned4 = lock ? { lock } : {};
|
|
2384
2384
|
const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
|
|
2385
2385
|
const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
|
|
2386
2386
|
const moved2 = await this.replacingTarget(
|
|
2387
2387
|
targetStat !== null,
|
|
2388
|
-
() => this.place(source.handle, dir, staging)
|
|
2388
|
+
() => this.place(source.handle, dir, staging, copies)
|
|
2389
2389
|
).catch(async (err) => {
|
|
2390
2390
|
if (first) await this.rollBack(staged, source.path, [first, second]);
|
|
2391
2391
|
throw err;
|
|
@@ -2399,15 +2399,13 @@ var OpfsFs = class {
|
|
|
2399
2399
|
await this.meta.update(source.path, () => left);
|
|
2400
2400
|
}
|
|
2401
2401
|
if (first) await this.meta.update(first, () => void 0);
|
|
2402
|
-
let copied = !moved2;
|
|
2403
2402
|
if (swap) {
|
|
2404
2403
|
const hidden = await dir.getDirectoryHandle(staging);
|
|
2405
|
-
const placed = await this.place(hidden, dir, name2);
|
|
2404
|
+
const placed = await this.place(hidden, dir, name2, copies);
|
|
2406
2405
|
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
2407
|
-
copied ||= !placed;
|
|
2408
2406
|
}
|
|
2409
2407
|
if (second) await this.meta.update(second, () => void 0);
|
|
2410
|
-
|
|
2408
|
+
await this.restamp(destination, copies);
|
|
2411
2409
|
this.changed(source.path, destination);
|
|
2412
2410
|
this.touch(Date.now(), parentOf3(source.path), parent.path);
|
|
2413
2411
|
};
|
|
@@ -2624,22 +2622,17 @@ var OpfsFs = class {
|
|
|
2624
2622
|
const handle = await this.handle(path);
|
|
2625
2623
|
return handle.kind === "file" ? (await this.snapshot(handle)).lastModified : 0;
|
|
2626
2624
|
}
|
|
2627
|
-
async
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
if (
|
|
2633
|
-
|
|
2634
|
-
return timed2;
|
|
2635
|
-
}
|
|
2636
|
-
async restamp(path, timed2) {
|
|
2637
|
-
for (const rest2 of timed2) {
|
|
2638
|
-
const mtimeFor = await this.modified(path + rest2);
|
|
2625
|
+
async restamp(path, copies) {
|
|
2626
|
+
if (copies.size === 0) return;
|
|
2627
|
+
const copied = [...copies];
|
|
2628
|
+
const entries = await this.meta.get(copied.map(([rest2]) => path + rest2));
|
|
2629
|
+
for (const [i, [rest2, { before, made: made2 }]] of copied.entries()) {
|
|
2630
|
+
if (entries[i]?.mtimeFor !== before) continue;
|
|
2631
|
+
const mtimeFor = (await this.io(made2.getFile())).lastModified;
|
|
2639
2632
|
await this.meta.update(path + rest2, (entry) => entry && { ...entry, mtimeFor });
|
|
2640
2633
|
}
|
|
2641
2634
|
}
|
|
2642
|
-
async place(handle, dir, name2) {
|
|
2635
|
+
async place(handle, dir, name2, copies) {
|
|
2643
2636
|
const move = handle.move;
|
|
2644
2637
|
if (move) this.others++;
|
|
2645
2638
|
const moved2 = await move?.call(handle, dir, name2).then(
|
|
@@ -2649,7 +2642,7 @@ var OpfsFs = class {
|
|
|
2649
2642
|
if (moved2) return true;
|
|
2650
2643
|
const before = this.fileMoves;
|
|
2651
2644
|
try {
|
|
2652
|
-
await this.copy(handle, dir, name2);
|
|
2645
|
+
await this.copy(handle, dir, name2, copies);
|
|
2653
2646
|
} catch (err) {
|
|
2654
2647
|
if (handle.kind === "directory" && this.fileMoves === before) {
|
|
2655
2648
|
await this.io(dir.removeEntry(name2, { recursive: true }));
|
|
@@ -2659,7 +2652,7 @@ var OpfsFs = class {
|
|
|
2659
2652
|
return false;
|
|
2660
2653
|
}
|
|
2661
2654
|
fileMoves = 0;
|
|
2662
|
-
async copy(handle, dir, name2) {
|
|
2655
|
+
async copy(handle, dir, name2, copies, rest2 = "") {
|
|
2663
2656
|
if (handle.kind === "file" && movable(handle)) {
|
|
2664
2657
|
this.others++;
|
|
2665
2658
|
const moved2 = await handle.move?.(dir, name2).then(
|
|
@@ -2674,13 +2667,17 @@ var OpfsFs = class {
|
|
|
2674
2667
|
if (handle.kind === "file") {
|
|
2675
2668
|
const made2 = await this.io(dir.getFileHandle(name2, { create: true }));
|
|
2676
2669
|
const writable2 = await this.io(made2.createWritable());
|
|
2677
|
-
|
|
2670
|
+
const file = await this.io(handle.getFile());
|
|
2671
|
+
await writable2.write(file);
|
|
2678
2672
|
await writable2.close();
|
|
2673
|
+
copies?.set(rest2, { before: copies.get(rest2)?.before ?? file.lastModified, made: made2 });
|
|
2679
2674
|
return;
|
|
2680
2675
|
}
|
|
2681
2676
|
const target = await this.io(dir.getDirectoryHandle(name2, { create: true }));
|
|
2682
2677
|
this.others++;
|
|
2683
|
-
for await (const child of handle.values())
|
|
2678
|
+
for await (const child of handle.values()) {
|
|
2679
|
+
await this.copy(child, target, child.name, copies, `${rest2}/${child.name}`);
|
|
2680
|
+
}
|
|
2684
2681
|
}
|
|
2685
2682
|
async symlink(target, path, owner) {
|
|
2686
2683
|
const parent = await this.locate(parentOf3(path), true);
|
|
@@ -7101,7 +7098,7 @@ function satisfies(version, range2) {
|
|
|
7101
7098
|
}
|
|
7102
7099
|
|
|
7103
7100
|
// src/version.ts
|
|
7104
|
-
var KERNEL_VERSION = true ? "1.134.
|
|
7101
|
+
var KERNEL_VERSION = true ? "1.134.4" : "0.0.0-dev";
|
|
7105
7102
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7106
7103
|
function procVersion() {
|
|
7107
7104
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -13665,7 +13662,7 @@ var AsyncRangedFile = class {
|
|
|
13665
13662
|
};
|
|
13666
13663
|
|
|
13667
13664
|
// src/realm/sync-sab-bridge.ts
|
|
13668
|
-
var
|
|
13665
|
+
var UNCOUNTED_OPS = /* @__PURE__ */ new Set([...SYNC_FS_OPS, "cwd-check", "cwd-chdir"]);
|
|
13669
13666
|
var SPIN_CORES = 4;
|
|
13670
13667
|
var spinning = (globalThis.navigator?.hardwareConcurrency ?? 0) >= SPIN_CORES;
|
|
13671
13668
|
|
|
@@ -13713,6 +13710,8 @@ var SYS_TREE = {
|
|
|
13713
13710
|
slicc_opfs_calls: (listing) => `${listing.opfsCalls ?? 0}
|
|
13714
13711
|
`,
|
|
13715
13712
|
slicc_opfs_ops: (listing) => `${listing.opfsOps ?? 0}
|
|
13713
|
+
`,
|
|
13714
|
+
slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
|
|
13716
13715
|
`
|
|
13717
13716
|
}
|
|
13718
13717
|
};
|
|
@@ -24772,6 +24771,9 @@ var WasmMemories = class {
|
|
|
24772
24771
|
const source = this.learned ? "learned" : ceiling === void 0 ? "none" : "default";
|
|
24773
24772
|
return { live: this.live, ceiling: ceiling ?? 0, source };
|
|
24774
24773
|
}
|
|
24774
|
+
started() {
|
|
24775
|
+
this.progress();
|
|
24776
|
+
}
|
|
24775
24777
|
ended() {
|
|
24776
24778
|
this.live = Math.max(0, this.live - 1);
|
|
24777
24779
|
this.progress();
|
|
@@ -28088,6 +28090,7 @@ var Launcher = class {
|
|
|
28088
28090
|
});
|
|
28089
28091
|
const counted2 = (handle2) => {
|
|
28090
28092
|
void handle2.exited.finally(() => this.memories.ended());
|
|
28093
|
+
void Promise.resolve(handle2.ready).then(() => this.memories.started());
|
|
28091
28094
|
return handle2;
|
|
28092
28095
|
};
|
|
28093
28096
|
if (!this.memories.near()) {
|
|
@@ -28186,7 +28189,8 @@ var Launcher = class {
|
|
|
28186
28189
|
peers: Number(this.locks.shared?.() === true),
|
|
28187
28190
|
fsCalls: servedFsCalls(),
|
|
28188
28191
|
opfsCalls: this.rootFs.opfsCalls?.() ?? 0,
|
|
28189
|
-
opfsOps: this.rootFs.opfsOps?.() ?? 0
|
|
28192
|
+
opfsOps: this.rootFs.opfsOps?.() ?? 0,
|
|
28193
|
+
cwdChecks: this.cwds.checks
|
|
28190
28194
|
}),
|
|
28191
28195
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
28192
28196
|
crontab: (call) => this.crontab(pid, call),
|