@ai-ecoverse/slicc-kernel 1.133.3 → 1.133.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 +1 -1
- package/dist/kernel-worker.js +72 -14
- package/dist/kernel-worker.js.map +2 -2
- package/dist/meta.js.map +2 -2
- package/dist/node-process-worker.js +157 -128
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +72 -14
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +145 -116
- package/dist/process-worker.js.map +4 -4
- package/dist/types/fs/changes.d.ts +1 -0
- package/dist/types/fs/meta.d.ts +7 -0
- package/dist/types/fs/opfs.d.ts +3 -0
- package/dist/types/fs/remote.d.ts +1 -0
- package/dist/types/process/wasi/wasi-files.d.ts +2 -1
- package/dist/types/realm/live-vfs-fs.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -709,7 +709,7 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
|
|
|
709
709
|
|
|
710
710
|
`/` is the OPFS root. Each process mounts the top-level directories that exist when it starts (plus `/usr` and `/bin`), so files in them are shared by all processes and visible through the OPFS API as soon as the process that wrote them has closed them; `run` resolves only after that. `createKernel` creates `/tmp` and `/home` in OPFS, so they are shared too. Files directly in `/`, and directories created there after a process started, are OPFS as well, the same for Emscripten and WASI programs; only `/dev` and `/proc` live in each process's memory. A program's `mkdir` (WASI `path_create_directory`, Emscripten's `mkdir`) is exclusive, as on Linux: it fails with `EEXIST` when the name exists, and of several processes or threads making one name at once exactly one succeeds, so a directory works as a lock. Under a missing directory it fails with `ENOENT`, as on Linux (`mkdir -p` and `os.makedirs` make the parents in the program); a JS program's `ctx.fs.mkdir` makes parents itself. The kernel makes the system `PNPM_HOME` (`/usr/local/share/pnpm`, or the embedder's) when it starts, since pnpm makes only its `bin` directory there. A full origin storage (`QuotaExceededError`, which also comes early in a browser's private or off-the-record contexts, whatever `navigator.storage.estimate()` says) is `ENOSPC`, never retried. A read that meets `NotFoundError` while a writer outside the kernel swaps the file in with `createWritable` (and a `stat`, `lstat` or `utimes` that meets it) looks the name up again, up to three times, before it fails ([#1075](https://github.com/ai-ecoverse/slicc-kernel/issues/1075)), so a program whose package is overwritten in place keeps starting. An OPFS or IndexedDB failure that has no POSIX counterpart is `EIO`; its message names the DOM error and the operation (`lookup`, `read`, `write`, `commit` or `sidecar`), and the kernel worker logs the first of each kind to its console. File contents are buffered per open file and written back on close, `fsync` and exit, so a refused write (`ENOSPC`, `EIO`) surfaces at that close or `fsync`, and a program that ignores close errors doesn't see it (`echo x > f` can exit 0 with `f` empty, as with any buffered write-back). A write-back error a close has already reported to a WASI program isn't raised again when it exits, so the program keeps its own exit status. Metadata operations (`mkdir`, `rename`, `rm`, …) go straight to OPFS. The kernel worker is the only writer.
|
|
711
711
|
|
|
712
|
-
**Renaming a directory on the OPFS root** ([#1122](https://github.com/ai-ecoverse/slicc-kernel/issues/1122)): Chrome's OPFS can move a file but not a directory. So the kernel renames a directory by creating the new tree and moving each file into it (copying a file only if it can't move, e.g. while it's open elsewhere), then removing the old tree. Within the kernel the rename is atomic to readers: a lookup or listing under the old or the new name, or of either parent, waits until the whole tree has moved. So a stage-then-rename updater (pnpm's, an atomic config write) is never seen half there. Another tab sharing the root reads OPFS directly and can see the tree mid-move. A root whose directories can move (the Node memory root) renames them in one step. **If the page reloads or crashes mid-rename**, some files are already in the new tree and the rest are still in the old one. So before the first move, the kernel writes the rename's intent (`{ from, to }`) to its metadata store, and clears it after the last. At the next boot, when no other tab shares the root (the same rule as for the boot's other repair of stale metadata), the kernel finishes every recorded rename: it moves what's left into the new tree, removes the old name, and clears the record. While another tab shares the root,
|
|
712
|
+
**Renaming a directory on the OPFS root** ([#1122](https://github.com/ai-ecoverse/slicc-kernel/issues/1122)): Chrome's OPFS can move a file but not a directory. So the kernel renames a directory by creating the new tree and moving each file into it (copying a file only if it can't move, e.g. while it's open elsewhere), then removing the old tree. Within the kernel the rename is atomic to readers: a lookup or listing under the old or the new name, or of either parent, waits until the whole tree has moved. So a stage-then-rename updater (pnpm's, an atomic config write) is never seen half there. Another tab sharing the root reads OPFS directly and can see the tree mid-move. A root whose directories can move (the Node memory root) renames them in one step. **If the page reloads or crashes mid-rename**, some files are already in the new tree and the rest are still in the old one. So before the first move, the kernel writes the rename's intent (`{ from, to }`) to its metadata store, and clears it after the last. At the next boot, when no other tab shares the root (the same rule as for the boot's other repair of stale metadata), the kernel finishes every recorded rename: it moves what's left into the new tree, removes the old name, and clears the record. The moved directory and its files keep their owner, mode and inode, as after a rename that wasn't stopped. While another tab shares the root, a kernel finishes only renames whose kernel is gone ([#1141](https://github.com/ai-ecoverse/slicc-kernel/issues/1141)): the renaming kernel holds a Web Lock (`slicc-rename:<id>`, named in the record) for the whole rename, and the browser releases it when that tab closes or its worker ends. A kernel that boots beside a peer, and every kernel that sees a peer go, finishes each recorded rename whose lock is free, taking that lock while it does, so two survivors never replay the same rename and a live kernel's rename is never touched. A record without a lock (written by an older kernel, or where Web Locks are missing) waits for a boot that's alone, as before. While another tab finishes a rename, this kernel's readers wait as for its own; the other tabs can see the tree mid-move, as during any rename.
|
|
713
713
|
|
|
714
714
|
A lookup follows at most 40 symlinks, as Linux does: the 41st is `ELOOP`, for WASI programs, JS programs and the kernel API alike, with or without `..` in a link's target. Emscripten programs resolve paths in their glue, which stops one link earlier, at 40.
|
|
715
715
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -900,6 +900,7 @@ var RemoteWatch = class {
|
|
|
900
900
|
settled = false;
|
|
901
901
|
quiet;
|
|
902
902
|
crowd;
|
|
903
|
+
gone;
|
|
903
904
|
told;
|
|
904
905
|
counted = 0;
|
|
905
906
|
recount() {
|
|
@@ -989,6 +990,7 @@ var RemoteWatch = class {
|
|
|
989
990
|
this.peers.delete(peer);
|
|
990
991
|
this.rebuild();
|
|
991
992
|
this.recount();
|
|
993
|
+
this.gone?.();
|
|
992
994
|
for (const wait2 of this.waiting.values()) {
|
|
993
995
|
if (wait2.left.delete(peer) && wait2.left.size === 0) wait2.done();
|
|
994
996
|
}
|
|
@@ -1024,14 +1026,16 @@ var RemoteWatch = class {
|
|
|
1024
1026
|
async prune() {
|
|
1025
1027
|
if (!this.locks) return;
|
|
1026
1028
|
const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
|
|
1029
|
+
let left = false;
|
|
1027
1030
|
for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
|
|
1028
1031
|
if (!held.has(this.lockOf(tab))) {
|
|
1032
|
+
if (this.peers.delete(tab)) left = true;
|
|
1029
1033
|
this.others.delete(tab);
|
|
1030
|
-
this.peers.delete(tab);
|
|
1031
1034
|
}
|
|
1032
1035
|
}
|
|
1033
1036
|
this.rebuild();
|
|
1034
1037
|
this.recount();
|
|
1038
|
+
if (left) this.gone?.();
|
|
1035
1039
|
}
|
|
1036
1040
|
watched() {
|
|
1037
1041
|
return this.union.size > 0;
|
|
@@ -1121,6 +1125,7 @@ var REPLACED_ATTEMPTS = 4;
|
|
|
1121
1125
|
var RECONCILE_LISTINGS = 8;
|
|
1122
1126
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
1123
1127
|
var RENAMES = "/.slicc-renames";
|
|
1128
|
+
var RENAME_LOCK = "slicc-rename:";
|
|
1124
1129
|
function movable(handle) {
|
|
1125
1130
|
return typeof handle.move === "function";
|
|
1126
1131
|
}
|
|
@@ -1287,8 +1292,11 @@ var OpfsFs = class {
|
|
|
1287
1292
|
this.lockPrefix = `slicc-excl:${channel ?? ""}:`;
|
|
1288
1293
|
this.rootName = channel;
|
|
1289
1294
|
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
1290
|
-
this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch
|
|
1291
|
-
if (this.remote)
|
|
1295
|
+
this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`, { locks: this.locks ?? null });
|
|
1296
|
+
if (this.remote) {
|
|
1297
|
+
this.remote.quiet = () => this.quiet().then(() => this.settle?.());
|
|
1298
|
+
this.remote.gone = () => void this.orphans();
|
|
1299
|
+
}
|
|
1292
1300
|
if (this.channel) {
|
|
1293
1301
|
const remote = this.remote;
|
|
1294
1302
|
meta.listen?.((c) => {
|
|
@@ -2361,14 +2369,17 @@ var OpfsFs = class {
|
|
|
2361
2369
|
const dir = parent.handle;
|
|
2362
2370
|
const name2 = baseOf(destination);
|
|
2363
2371
|
const swap = targetStat?.isDirectory === true;
|
|
2372
|
+
let lock;
|
|
2364
2373
|
const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
|
|
2365
2374
|
const copying = sourceStat.isDirectory && !movable(source.handle);
|
|
2366
2375
|
const staged = resolvePath(parent.path, staging);
|
|
2367
2376
|
const place = async () => {
|
|
2368
2377
|
this.forget(destination);
|
|
2369
2378
|
const timed2 = await this.explicitTimes(source.path);
|
|
2370
|
-
const
|
|
2371
|
-
const
|
|
2379
|
+
const root = { ino: inodeOf(source.path), ...moving };
|
|
2380
|
+
const owned4 = lock ? { lock } : {};
|
|
2381
|
+
const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
|
|
2382
|
+
const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
|
|
2372
2383
|
const moved2 = await this.replacingTarget(
|
|
2373
2384
|
targetStat !== null,
|
|
2374
2385
|
() => this.place(source.handle, dir, staging)
|
|
@@ -2377,7 +2388,7 @@ var OpfsFs = class {
|
|
|
2377
2388
|
throw err;
|
|
2378
2389
|
});
|
|
2379
2390
|
if (swap) await this.rm(destination, { recursive: true });
|
|
2380
|
-
if (second) await this.intend({ from: staged, to: destination }, second);
|
|
2391
|
+
if (second) await this.intend({ from: staged, to: destination, root, ...owned4 }, second);
|
|
2381
2392
|
await this.meta.move(source.path, destination, carry, left);
|
|
2382
2393
|
this.movePending(source.path, destination);
|
|
2383
2394
|
if (!moved2) {
|
|
@@ -2402,7 +2413,11 @@ var OpfsFs = class {
|
|
|
2402
2413
|
const entry = { from: source.path, to: destination, done: promise };
|
|
2403
2414
|
this.renamingDirs.add(entry);
|
|
2404
2415
|
try {
|
|
2405
|
-
|
|
2416
|
+
const locks = this.locks;
|
|
2417
|
+
if (locks) {
|
|
2418
|
+
lock = `${RENAME_LOCK}${crypto.randomUUID()}`;
|
|
2419
|
+
await locks.request(lock, place);
|
|
2420
|
+
} else await place();
|
|
2406
2421
|
} finally {
|
|
2407
2422
|
this.renamingDirs.delete(entry);
|
|
2408
2423
|
resolve2();
|
|
@@ -2434,12 +2449,40 @@ var OpfsFs = class {
|
|
|
2434
2449
|
} catch {
|
|
2435
2450
|
}
|
|
2436
2451
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2452
|
+
replaying = Promise.resolve();
|
|
2453
|
+
orphans() {
|
|
2454
|
+
this.replaying = this.replaying.then(() => this.finishRenames(true)).catch(() => 0);
|
|
2455
|
+
return this.replaying;
|
|
2456
|
+
}
|
|
2457
|
+
async finishRenames(orphaned = false) {
|
|
2458
|
+
const locks = this.locks;
|
|
2459
|
+
const held = /* @__PURE__ */ new Set();
|
|
2460
|
+
if (orphaned && locks) {
|
|
2461
|
+
const { held: h = [], pending = [] } = await locks.query();
|
|
2462
|
+
for (const lock of [...h, ...pending]) held.add(lock.name);
|
|
2463
|
+
}
|
|
2464
|
+
const intents = (await this.meta.under(RENAMES)).filter(
|
|
2465
|
+
(entry) => entry.rename !== void 0 && (!orphaned || locks !== void 0 && entry.rename.lock !== void 0 && !held.has(entry.rename.lock))
|
|
2466
|
+
).sort(
|
|
2439
2467
|
(a, b) => a.rename.at - b.rename.at
|
|
2440
2468
|
);
|
|
2441
2469
|
for (const entry of intents) {
|
|
2442
|
-
const
|
|
2470
|
+
const lock = entry.rename.lock;
|
|
2471
|
+
if (!orphaned || !locks || !lock) await this.finishRename(entry);
|
|
2472
|
+
else await locks.request(lock, () => this.finishRename(entry));
|
|
2473
|
+
}
|
|
2474
|
+
return intents.length;
|
|
2475
|
+
}
|
|
2476
|
+
async finishRename(entry) {
|
|
2477
|
+
const [current] = await this.meta.under(entry.path);
|
|
2478
|
+
if (current?.rename === void 0) return;
|
|
2479
|
+
const { from, to, replace, root } = current.rename;
|
|
2480
|
+
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
2481
|
+
const barrier = { from, to, done: promise };
|
|
2482
|
+
this.renamingDirs.add(barrier);
|
|
2483
|
+
this.forget(from);
|
|
2484
|
+
this.forget(to);
|
|
2485
|
+
try {
|
|
2443
2486
|
const left = await this.real(from);
|
|
2444
2487
|
if (left && replace && await this.real(to)) await this.rm(to, { recursive: true });
|
|
2445
2488
|
if (left) {
|
|
@@ -2456,11 +2499,23 @@ var OpfsFs = class {
|
|
|
2456
2499
|
this.forget(to);
|
|
2457
2500
|
}
|
|
2458
2501
|
if ((await this.meta.under(from)).length > 0) {
|
|
2459
|
-
await this.meta.move(
|
|
2502
|
+
await this.meta.move(
|
|
2503
|
+
from,
|
|
2504
|
+
to,
|
|
2505
|
+
(moved2) => ({
|
|
2506
|
+
...occupied(moved2),
|
|
2507
|
+
...root,
|
|
2508
|
+
ino: occupied(moved2).ino ?? root?.ino ?? inodeOf(from),
|
|
2509
|
+
path: to
|
|
2510
|
+
}),
|
|
2511
|
+
void 0
|
|
2512
|
+
);
|
|
2460
2513
|
}
|
|
2461
2514
|
await this.meta.update(entry.path, () => void 0);
|
|
2515
|
+
} finally {
|
|
2516
|
+
this.renamingDirs.delete(barrier);
|
|
2517
|
+
resolve2();
|
|
2462
2518
|
}
|
|
2463
|
-
return intents.length;
|
|
2464
2519
|
}
|
|
2465
2520
|
async settledRename(path) {
|
|
2466
2521
|
if (this.renamingDirs.size === 0 && this.copying.size === 0 || typeof path !== "string")
|
|
@@ -2744,7 +2799,10 @@ var OpfsFs = class {
|
|
|
2744
2799
|
};
|
|
2745
2800
|
}
|
|
2746
2801
|
async reconcile() {
|
|
2747
|
-
if (this.remote && await this.remote.peered())
|
|
2802
|
+
if (this.remote && await this.remote.peered()) {
|
|
2803
|
+
await this.orphans();
|
|
2804
|
+
return 0;
|
|
2805
|
+
}
|
|
2748
2806
|
await this.finishRenames();
|
|
2749
2807
|
let removed = 0;
|
|
2750
2808
|
const entries = await this.meta.all();
|
|
@@ -7034,7 +7092,7 @@ function satisfies(version, range2) {
|
|
|
7034
7092
|
}
|
|
7035
7093
|
|
|
7036
7094
|
// src/version.ts
|
|
7037
|
-
var KERNEL_VERSION = true ? "1.133.
|
|
7095
|
+
var KERNEL_VERSION = true ? "1.133.4" : "0.0.0-dev";
|
|
7038
7096
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7039
7097
|
function procVersion() {
|
|
7040
7098
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|