@ai-ecoverse/slicc-kernel 1.124.2 → 1.124.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 +7 -2
- package/dist/kernel-worker.js +93 -35
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +39 -10
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +93 -35
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +39 -10
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/remote.d.ts +8 -0
- package/dist/types/kernel/proc-info.d.ts +1 -0
- package/dist/types/realm/live-vfs-fs.d.ts +1 -0
- package/dist/types/realm/sync-fs-dispatch.d.ts +1 -0
- package/dist/types/realm/sync-sab-bridge.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -645,7 +645,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
645
645
|
- **The same file:** a lock is keyed by the file's device and inode, which both kernels read from the shared sidecar, so a rename by either tab keeps it. A file removed and created again at the same path has the same key, as it already has within one kernel.
|
|
646
646
|
- **Versions:** the channel and Web Lock names carry a protocol version (`v1`), and a kernel ignores messages that aren't v1. So kernels on different protocol versions (two tabs across an update) don't share locks, as before #675.
|
|
647
647
|
- **Not across tabs:** the imports module's whole-file `tryLock` (per kernel), and locks on other mounts.
|
|
648
|
-
- **Another tab's writes** ([#959](https://github.com/ai-ecoverse/slicc-kernel/issues/959)): a program's names and stats are cached in its process, and its kernel refreshes them when the kernel itself changes a file. While another kernel shares the root (a second tab), the kernel marks that view foreign, and a program then looks names up and reads stats again on every look. So a program that keeps checking a file sees what the other tab wrote, synced and closed. A kernel alone caches as before. While another kernel shares the root, a kernel also tells it of each file it writes ([#962](https://github.com/ai-ecoverse/slicc-kernel/issues/962)). The other kernel then drops the clean contents of that file, both its own and its programs', so a descriptor tab B already has open reads tab A's new bytes on its next read, as Linux's shared page cache does. The notice is a message, so a read that races it by less than a message's delivery can still see the old bytes, as with any reader that doesn't lock.
|
|
648
|
+
- **Another tab's writes** ([#959](https://github.com/ai-ecoverse/slicc-kernel/issues/959)): a program's names and stats are cached in its process, and its kernel refreshes them when the kernel itself changes a file. While another kernel shares the root (a second tab), the kernel marks that view foreign, and a program then looks names up and reads stats again on every look. So a program that keeps checking a file sees what the other tab wrote, synced and closed. A kernel alone caches as before. Stats are always read fresh while the root is shared. An Emscripten program keeps the names it has looked up, directories and files ([#984](https://github.com/ai-ecoverse/slicc-kernel/issues/984)), until its next call that isn't by path: a read, a write, a poll, a sleep, a wait, a spawn, a look at the clock or a draw of random bytes. Within one run of path-only calls (lstat, chmod, readlink and the like) with none of those in between, an answer the program takes from its own node, such as the node's type, can lag behind the other tab until that next call, as a path call racing a rename does on Linux. A program can only learn that the other tab acted through one of those calls, a new process or a stat, and all of them refresh what it sees. While another kernel shares the root, a kernel also tells it of each file it writes ([#962](https://github.com/ai-ecoverse/slicc-kernel/issues/962)). The other kernel then drops the clean contents of that file, both its own and its programs', so a descriptor tab B already has open reads tab A's new bytes on its next read, as Linux's shared page cache does. The notice is a message, so a read that races it by less than a message's delivery can still see the old bytes, as with any reader that doesn't lock.
|
|
649
649
|
- **`slicc.fcntl_lock(fd, cmd, flock_ptr) -> errno`** ([#722](https://github.com/ai-ecoverse/slicc-kernel/issues/722)): a WASIX program's `fcntl` record locks reach the same table as an Emscripten program's, so the two see each other's locks.
|
|
650
650
|
- **cmd:** `12` `F_GETLK`, `13` `F_SETLK` or `14` `F_SETLKW`. Any other cmd is `EINVAL`.
|
|
651
651
|
- **The struct:** musl's wasm32 `struct flock`, 32 bytes: `l_type` i16 at 0 (`0` read, `1` write, `2` unlock), `l_whence` i16 at 2 (`0` SET, `1` CUR, `2` END, resolved against the descriptor's offset or size), `l_start` i64 at 8, `l_len` i64 at 16 (`0` to EOF and beyond, negative before `l_start`), `l_pid` i32 at 24. The kernel reads and writes only those five fields and never the padding.
|
|
@@ -949,7 +949,7 @@ In a browser every process worker of the page shares one renderer. V8 reserves a
|
|
|
949
949
|
- **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.
|
|
950
950
|
- **`/proc/sys/kernel/slicc_wasm_memories`** reads `<live> <ceiling> <source>`. The source is `default`, `learned`, or `none` (with the ceiling 0).
|
|
951
951
|
|
|
952
|
-
`/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
|
|
952
|
+
`/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.
|
|
953
953
|
|
|
954
954
|
### Metadata
|
|
955
955
|
|
|
@@ -1005,6 +1005,8 @@ npm run test:unit
|
|
|
1005
1005
|
|
|
1006
1006
|
`npm test` builds `dist/` and runs the integration tests in Chromium from `playwright-core`, over raw CDP through the [harness from slicc-shared-web](https://github.com/ai-ecoverse/slicc-shared-web#integration-test-harness), against a cross-origin isolated test page that installs bash and coreutils into OPFS. Each test writes screenshots, console logs and CPU profiles to `artifacts/`; V8 coverage from the page and every worker, mapped back to `src/` through the source maps, goes to `coverage/`, and `artifacts/hotspots.md` lists where the time went.
|
|
1007
1007
|
|
|
1008
|
+
The recorder behind that coverage changes timing and memory in long runs. It attaches to every page and worker with the debugger, profiler and precise coverage on, and pauses every process worker at its exit breakpoint (`exits` in `test/integration/chrome.mjs`) until the coverage of that worker is collected, keeping every collected script until the run ends. A run with thousands of process exits or hundreds of page loads therefore grows the test process by gigabytes, and each exit waits longer as it grows, so a parent can sit in `proc-exec` for tens of seconds while its child has already finished; each further page load also gets slower (in one tab reloaded 300 times, a boot went from 80 to 500 ms with no kernel in any other tab). When a long run looks stuck or leaky, measure it again with a `launch` that passes `coverage: ['/nothing/']` and no `exits` before blaming the kernel (#991).
|
|
1009
|
+
|
|
1008
1010
|
Unit tests live in `test/unit/`, which stays out of git. They run real wasm bash and coreutils through the whole stack in Node `worker_threads` against an in-memory OPFS. The pre-commit hook runs them under coverage and requires every changed line in `src/` to be covered. `tools/heavy.sh` runs them two files at a time, in one of two unit slots of the host (`/tmp/slicc-unit-N.lock`, waiting while both are taken; a lock it can't take for any other reason, such as a lock file it can't open, ends the run with `lockf`'s status; `SLICC_UNIT_SLOTS` sets how many, `SLICC_UNIT_SLOT=0` takes none, as for CI; a HUP, INT or TERM to `heavy.sh` stops the run it holds or waits for), never in the browser runs' slots (those go through `slicc-heavy`; `SLICC_HEAVY_SLOT=1` runs the unit tests through `~/.bb/coord-bin/slicc-heavy` instead, when it is there), and stops the run if its processes use more than 3,000,000 KB together or 2,000,000 KB in one process (2,800,000 KB for the command itself, the coverage tool that merges the report at the end; `SLICC_HEAVY_ROOT_KB`, never below the per-process cap), or run longer than 900 s once it has its slot (`SLICC_HEAVY_TIMEOUT`; each test also has 180 s) (`SLICC_HEAVY_REPORT=1` prints the peak). `SLICC_HEAVY_TREE_KB` and `SLICC_HEAVY_PROC_KB` raise the caps, but only for measuring a run under a higher ceiling that still stops a runaway, never to get a commit through. The guard follows the command's process group and every process it has seen, including children that leave the group, but a process that forks, calls `setsid` and whose parent exits within one 0.25 s sample can escape: macOS has no unprivileged containment, and a test that daemonizes is a bug. The compiler tests (zig, rustc and cargo), each a few GB, are integration tests in `test/integration/compilers.test.mjs`, which CI runs.
|
|
1009
1011
|
|
|
1010
1012
|
The Biome, TypeScript, lefthook, Renovate and CI configuration comes from [slicc-shared-web](https://github.com/ai-ecoverse/slicc-shared-web), which also provides the `slicc-lint-comments` (no comments anywhere), `slicc-no-unit-tests` (no unit tests in git) and `slicc-diff-cover` (100% coverage of changed lines) commands that `npm run lint` and the pre-commit hook use.
|
|
@@ -1015,6 +1017,8 @@ The Biome, TypeScript, lefthook, Renovate and CI configuration comes from [slicc
|
|
|
1015
1017
|
|
|
1016
1018
|
`test/integration/fixtures/preh1stat/` is a tiny Emscripten program (`preh1stat.c`) that prints its `getuid`/`geteuid`/`getgid`/`getegid` and the owner of each path it's given. It's built **without** the credential library (`slicc_cred_js`), with today's emcc from homescoop's pinned `emsdk@0.4.0`, so it stands for the packages installed before H1. Those see every file as `1000:1000` (`stat-owner.test.mjs`), and every published `@ai-ecoverse/wasm-*` package now links the library, so no package can carry that row. Build it with `test/integration/fixtures/preh1stat/build.sh`: it fetches `emsdk@0.4.0` unless `emcc` is on `PATH`, `PREH1STAT_BUILD` sets its work directory, and it refuses a build whose glue carries the credential library.
|
|
1017
1019
|
|
|
1020
|
+
`test/integration/fixtures/clocklink/` is a tiny Emscripten program (`clocklink.c`) for `crowded-lookups.test.mjs` (#984). It creates the file named by its second argument (the signal to the other kernel), reads the link named by its first, then reads only the wall clock until the deadline given as its third argument (milliseconds since the epoch), reads the link again and prints both answers (`first=EINVAL second=t`). Build it with `test/integration/fixtures/clocklink/build.sh`, which fetches `emsdk@0.4.0` (emcc 4.0.23) unless `emcc` is on `PATH`; `CLOCKLINK_BUILD` sets its work directory. The committed build is `bin/clocklink` sha256 `0096e2ff68fb380d1b71aaa5fae64d96d6fe60aa2a3933e7207394d30436bf72` and `bin/clocklink.wasm` sha256 `e251dfa3b9d19d855cd0704a9cf67e7a7d28270083292ecde18809932bfbba16`.
|
|
1021
|
+
|
|
1018
1022
|
`test/integration/fixtures/linktest/` is a WASIX program (`linktest.c`) that calls `linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW)` and prints the new name's type, whether it shares the target's inode, and its link count, for `link-follow.test.mjs`. Coreutils' `ln -L` (Emscripten) takes another path, so the WASI program covers `path_link` with the follow flag. Build it with homescoop's pinned wasixcc and wasix-sysroot 2025.9.30-28: `INSTALL_WASIXCC=<homescoop>/scripts/install-wasixcc.sh WASIX_SYSROOT_28=<unpacked -28 package> test/integration/fixtures/linktest/build.sh`.
|
|
1019
1023
|
|
|
1020
1024
|
`tools/big-file.mjs [bytes]` is a manual check, not part of any suite. It writes a file of that size (64 MiB by default) through a tmpfs mount with the unit-test launcher, reads it back, and prints the peak RSS and its ratio to the file size. A 256 MiB file peaks at about 1.8 times its size. Before ranged I/O (#127), a 1.1 GiB file peaked at 18 GB of RSS. **Do not run it past 1 GiB on a workstation.** It refuses anything over 256 MiB unless `SLICC_BIG_FILE_I_HAVE_THE_RAM=1` is set. Measure a small file and extrapolate instead. The unit suite checks a 32 MiB file the same way, and allows a fixed 192 MiB for garbage V8 has yet to collect, which dominates at that size.
|
|
@@ -1058,6 +1062,7 @@ The tools in `tools/cert-review/`:
|
|
|
1058
1062
|
|
|
1059
1063
|
- **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. The merge group's lookup loads its tools from the protected branch itself (`merge_group.base_ref`), not from the group's `base_sha`, which can be the speculative commit of the group ahead and carry the reviewer changes of the pull requests queued before it; `base_sha` is only the lookup's input. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts with `merge-ok`. The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
|
|
1060
1064
|
- **A gate's silence never reads as a pass:** every tool a workflow gates on (`pr.mjs plan`, `same` and `lookup`, `same.mjs`, `pack.mjs`, `delta.mjs`, `suite.mjs`, `compat.mjs`, `findings.mjs`, `record.mjs`, `verify.mjs`) ends with one verdict line, `<tool>: PASS <what>` or `<tool>: FAIL <why>`, from the shared `verdict()` in `lib.mjs`. `tools/cert-review/gate.sh [--either|--evidence] <tool> <command>` runs a tool, passes its output through, and exits 70 with an error when the tool's last line starting `<tool>: ` isn't a PASS or FAIL verdict, so a tool replaced by `true`, an early exit 0 or an empty run fails its step. By default the step passes only on PASS with exit 0; a PASS with a non-zero exit, or a FAIL with exit 0, fails it. `--either` keeps the tool's own exit, for tools whose FAIL is evidence that record judges later (suite, compat). `--evidence` needs only the verdict, for the NEGATIVE run on main's tree, which is expected to fail. `suite: PASS` means exit 0, no failures and every expected file has results; `compat: PASS` means a run turned up with no regressions. `prompt.mjs` writes the review prompt to stdout, which goes into a step output, so it prints no verdict; a missing prompt leaves the review without `verdict.json`, and record marks that INCOMPLETE. The gate reads the tool's stdout, and no tool passes the pull request's output there: suite pipes the tests' output into its log file, and compat prints only its own lines, so a test that prints `suite: PASS` can't stand in for a silent suite. A tool that let such output through would need its verdict on a separate descriptor. Because the workflow of a pull request runs main's tools, the tools gain their verdicts first, and the workflows call them through `gate.sh` in the pull request after that.
|
|
1065
|
+
- **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate`, which builds it, and both `suite` legs, which run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So no consumer trusts a name or a pattern. Each producer job hands on its artifact's `artifact-id` and `artifact-digest` as job outputs, consumers download by `artifact-ids` with `digest-mismatch: error`, and no upload uses `overwrite`, so a second upload of a name fails instead of replacing it. The base build has its own job, `pack base`, which runs only main's code, so the candidate's job can't speak for it. `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails when one in our namespaces (`kernel-tgz-`, `cert-review-`) wasn't uploaded by its producer, with that producer's id, name and digest, or when a producer's artifact is gone. `same` runs it on the two tarballs before it compares them, so the "not behaviour (identical dist)" skip can't be bought with a planted base tarball. `record` runs it on every artifact before it writes the line, and a failure there makes the record INCOMPLETE. **What this protects:** artifacts of jobs that run no pull-request code (the base build, the delta, kernel-compat, the same-dist evidence, the review's verdict and evidence) can't be forged or replaced. **What it doesn't:** the candidate's own artifacts, its tarball and both suites' results (the base leg runs the pull request's tests on main's tree, for the NEGATIVE), stay the pull request's by design, because code that runs on a runner can always lie about its own results. They're consumed by their exact names, at most one each. That's the same deliberate-malice residual as the workflow-rewrite one above.
|
|
1061
1066
|
- **Decide by what ships:** when a pull request's behaviour files are only `package.json` and/or `package-lock.json` (a Renovate devDependency bump, say), both sides are packed as usual, and then the `same dist` job (`same.mjs`, in code) compares the two tarballs before anything else runs. It checks the same paths, entry types (a symlink isn't a file) and modes. `package.json` must be equal once `devDependencies`, `version` and `gitHead` are removed, so `scripts` (they ship, a consumer's install runs `postinstall`, and `prepack`/`prepublishOnly` can make the published build differ from the packed one), runtime dependencies, `exports`, `bin`, `files` and `engines` must all match. Every other file must be byte-identical once each side's **exact** version string (build.mjs bakes it into `dist`) is replaced by one placeholder. A version string in one more place, or any looser match, is a difference. Source maps are compared as JSON without their `mappings`, whose columns the longer or shorter version string shifts; the code itself is compared byte for byte. If all of that matches, `cert-review` is green with `not behaviour (identical dist): <candidate sha256> = <base sha256> after normalising <v1>/<v2>`, and the suites, kernel-compat, the review and the record are skipped. `same.json` (uploaded as an artifact) names the rule and both tarballs' full sha256. Any difference, or a `same dist` job that doesn't finish, means the full review. The kernel has no runtime `dependencies`, since esbuild bundles everything into `dist`, so a devDependency that changes the product shows up as a byte difference. A test-only devDependency is checked by `ci / test`, on the pull request and on the merge group.
|
|
1062
1067
|
- **When:** only for a same-repo pull request with `merge-ok`, when the label is added and on every push while it is set. Each pull request has its own concurrency group, and a newer push cancels the older review. A behaviour change without `merge-ok` gets a red status, `waits for merge-ok to be reviewed`. Other labels don't start or cancel anything. Fork code never reaches the pools or the key.
|
|
1063
1068
|
- **What:** the checkout is the event's default `refs/pull/N/merge`, the pull request merged onto `main` at review time, so a review is of what would land, without pushing to the branch. Both sides are packed in the same run, on a GitHub-hosted runner, with no secrets: the candidate (the merge ref, `x.y.z-pr.N.<run>`) and the base (its first parent, `main`). Then:
|
package/dist/kernel-worker.js
CHANGED
|
@@ -856,6 +856,8 @@ var ProtectedFs = class {
|
|
|
856
856
|
// src/fs/remote.ts
|
|
857
857
|
var ANNOUNCE_WAIT_MS = 500;
|
|
858
858
|
var PRUNE_EVERY_MS = 3e4;
|
|
859
|
+
var MOURN_TRIES = 5;
|
|
860
|
+
var MOURN_RETRY_MS = 100;
|
|
859
861
|
function lockManager() {
|
|
860
862
|
return globalThis.navigator?.locks;
|
|
861
863
|
}
|
|
@@ -864,6 +866,7 @@ var RemoteWatch = class {
|
|
|
864
866
|
name;
|
|
865
867
|
tab = crypto.randomUUID();
|
|
866
868
|
peers = /* @__PURE__ */ new Set();
|
|
869
|
+
mourned = /* @__PURE__ */ new Set();
|
|
867
870
|
others = /* @__PURE__ */ new Map();
|
|
868
871
|
union = /* @__PURE__ */ new Set();
|
|
869
872
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -932,8 +935,7 @@ var RemoteWatch = class {
|
|
|
932
935
|
receive(data) {
|
|
933
936
|
if (data.hello !== void 0) this.greeted(data.hello);
|
|
934
937
|
if (data.here !== void 0 && data.to === this.tab) {
|
|
935
|
-
this.
|
|
936
|
-
this.recount();
|
|
938
|
+
this.arrived(data.here);
|
|
937
939
|
this.met?.();
|
|
938
940
|
}
|
|
939
941
|
if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
|
|
@@ -942,9 +944,36 @@ var RemoteWatch = class {
|
|
|
942
944
|
}
|
|
943
945
|
if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
|
|
944
946
|
}
|
|
945
|
-
|
|
947
|
+
arrived(peer) {
|
|
946
948
|
this.peers.add(peer);
|
|
947
949
|
this.recount();
|
|
950
|
+
void this.mourn(peer);
|
|
951
|
+
}
|
|
952
|
+
async mourn(peer, tries = MOURN_TRIES) {
|
|
953
|
+
if (!this.locks || this.mourned.has(peer)) return;
|
|
954
|
+
this.mourned.add(peer);
|
|
955
|
+
const name2 = this.lockOf(peer);
|
|
956
|
+
const { held = [], pending = [] } = await this.locks.query();
|
|
957
|
+
if (![...held, ...pending].some((lock) => lock.name === name2)) {
|
|
958
|
+
this.mourned.delete(peer);
|
|
959
|
+
if (tries > 1 && this.peers.has(peer)) {
|
|
960
|
+
setTimeout(() => void this.mourn(peer, tries - 1), MOURN_RETRY_MS);
|
|
961
|
+
}
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
void this.locks.request(name2, async () => {
|
|
965
|
+
this.mourned.delete(peer);
|
|
966
|
+
this.others.delete(peer);
|
|
967
|
+
this.peers.delete(peer);
|
|
968
|
+
this.rebuild();
|
|
969
|
+
this.recount();
|
|
970
|
+
for (const wait2 of this.waiting.values()) {
|
|
971
|
+
if (wait2.left.delete(peer) && wait2.left.size === 0) wait2.done();
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
greeted(peer) {
|
|
976
|
+
this.arrived(peer);
|
|
948
977
|
const answer = () => {
|
|
949
978
|
this.channel.postMessage({ here: this.tab, to: peer });
|
|
950
979
|
if (this.mine.length > 0) void this.announce(this.mine);
|
|
@@ -954,8 +983,7 @@ var RemoteWatch = class {
|
|
|
954
983
|
}
|
|
955
984
|
announced({ tab, paths, seq: seq2 }) {
|
|
956
985
|
const from = tab;
|
|
957
|
-
this.
|
|
958
|
-
this.recount();
|
|
986
|
+
this.arrived(from);
|
|
959
987
|
if (paths.length > 0) this.others.set(from, paths);
|
|
960
988
|
else this.others.delete(from);
|
|
961
989
|
this.rebuild();
|
|
@@ -1567,7 +1595,8 @@ var OpfsFs = class {
|
|
|
1567
1595
|
async linkAlong(path) {
|
|
1568
1596
|
for (const entry of await this.meta.get(prefixes(path))) {
|
|
1569
1597
|
if (entry?.link === void 0) continue;
|
|
1570
|
-
if (
|
|
1598
|
+
if (entry.path !== path && await this.real(entry.path)) continue;
|
|
1599
|
+
if (await this.real(parentOf3(entry.path))) return entry;
|
|
1571
1600
|
}
|
|
1572
1601
|
return void 0;
|
|
1573
1602
|
}
|
|
@@ -2992,7 +3021,7 @@ var CdpFacade = class {
|
|
|
2992
3021
|
for (const conn of this.connections) conn.close();
|
|
2993
3022
|
}
|
|
2994
3023
|
async acceptLoop() {
|
|
2995
|
-
const
|
|
3024
|
+
const served2 = /* @__PURE__ */ new Set();
|
|
2996
3025
|
for (; ; ) {
|
|
2997
3026
|
let conn;
|
|
2998
3027
|
try {
|
|
@@ -3000,10 +3029,10 @@ var CdpFacade = class {
|
|
|
3000
3029
|
} catch {
|
|
3001
3030
|
break;
|
|
3002
3031
|
}
|
|
3003
|
-
const done4 = this.serve(conn).finally(() =>
|
|
3004
|
-
|
|
3032
|
+
const done4 = this.serve(conn).finally(() => served2.delete(done4));
|
|
3033
|
+
served2.add(done4);
|
|
3005
3034
|
}
|
|
3006
|
-
await Promise.allSettled([...
|
|
3035
|
+
await Promise.allSettled([...served2]);
|
|
3007
3036
|
}
|
|
3008
3037
|
async serve(conn) {
|
|
3009
3038
|
this.connections.add(conn);
|
|
@@ -6565,7 +6594,7 @@ function satisfies(version, range2) {
|
|
|
6565
6594
|
}
|
|
6566
6595
|
|
|
6567
6596
|
// src/version.ts
|
|
6568
|
-
var KERNEL_VERSION = true ? "1.124.
|
|
6597
|
+
var KERNEL_VERSION = true ? "1.124.4" : "0.0.0-dev";
|
|
6569
6598
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6570
6599
|
function procVersion() {
|
|
6571
6600
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -7485,15 +7514,21 @@ var UnlinkedKeeper = class {
|
|
|
7485
7514
|
};
|
|
7486
7515
|
const unreplaced = async (path, op) => {
|
|
7487
7516
|
const at = fs.resolvePath("/", path);
|
|
7517
|
+
const pending = async () => {
|
|
7518
|
+
for (let p = at; replacing.size > 0; p = parentOf5(p)) {
|
|
7519
|
+
await replacing.get(p)?.done;
|
|
7520
|
+
if (p === "/") return;
|
|
7521
|
+
}
|
|
7522
|
+
};
|
|
7488
7523
|
for (let attempt = 1; ; attempt++) {
|
|
7489
|
-
await
|
|
7524
|
+
await pending();
|
|
7490
7525
|
const seen = keeper.replaced;
|
|
7491
7526
|
try {
|
|
7492
7527
|
return await op();
|
|
7493
7528
|
} catch (err) {
|
|
7494
7529
|
const code = err?.code;
|
|
7495
7530
|
if (attempt >= 3 || code !== "ENOENT") throw err;
|
|
7496
|
-
await
|
|
7531
|
+
await pending();
|
|
7497
7532
|
if (keeper.replaced === seen) throw err;
|
|
7498
7533
|
}
|
|
7499
7534
|
}
|
|
@@ -7518,9 +7553,11 @@ var UnlinkedKeeper = class {
|
|
|
7518
7553
|
lstat: (path) => unreplaced(path, async () => counted2(await fs.lstat(path))),
|
|
7519
7554
|
async rm(path, options) {
|
|
7520
7555
|
const target = fs.resolvePath("/", path);
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7556
|
+
await replace(target, async (gated) => {
|
|
7557
|
+
const pinned = await keeper.pin(target, gated);
|
|
7558
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
7559
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
7560
|
+
});
|
|
7524
7561
|
},
|
|
7525
7562
|
async rename(from, to) {
|
|
7526
7563
|
const target = fs.resolvePath("/", to);
|
|
@@ -7555,9 +7592,22 @@ var UnlinkedKeeper = class {
|
|
|
7555
7592
|
return held;
|
|
7556
7593
|
}
|
|
7557
7594
|
async pin(target, gated = () => void 0) {
|
|
7558
|
-
const paths = [
|
|
7595
|
+
const paths = [];
|
|
7559
7596
|
const releases = [];
|
|
7560
|
-
for (
|
|
7597
|
+
for (; ; ) {
|
|
7598
|
+
const fresh2 = [...this.heldWithin(target).keys()].filter((path) => !paths.includes(path));
|
|
7599
|
+
if (fresh2.length === 0) break;
|
|
7600
|
+
fresh2.sort();
|
|
7601
|
+
if (paths.some((path) => path > fresh2[0])) {
|
|
7602
|
+
for (const release of releases.splice(0)) release();
|
|
7603
|
+
fresh2.push(...paths.splice(0));
|
|
7604
|
+
fresh2.sort();
|
|
7605
|
+
}
|
|
7606
|
+
for (const path of fresh2) {
|
|
7607
|
+
paths.push(path);
|
|
7608
|
+
releases.push(await this.gate.acquire(path));
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7561
7611
|
gated();
|
|
7562
7612
|
const now = this.heldWithin(target);
|
|
7563
7613
|
const held = new Map(
|
|
@@ -8264,7 +8314,7 @@ var RealmProxy = class {
|
|
|
8264
8314
|
for (const conn of this.connections) conn.close();
|
|
8265
8315
|
}
|
|
8266
8316
|
async acceptLoop() {
|
|
8267
|
-
const
|
|
8317
|
+
const served2 = /* @__PURE__ */ new Set();
|
|
8268
8318
|
try {
|
|
8269
8319
|
for (; ; ) {
|
|
8270
8320
|
await this.slots.acquire(1, this.stop.signal);
|
|
@@ -8277,13 +8327,13 @@ var RealmProxy = class {
|
|
|
8277
8327
|
}
|
|
8278
8328
|
const done4 = this.serve(conn).finally(() => {
|
|
8279
8329
|
this.slots.release(1);
|
|
8280
|
-
|
|
8330
|
+
served2.delete(done4);
|
|
8281
8331
|
});
|
|
8282
|
-
|
|
8332
|
+
served2.add(done4);
|
|
8283
8333
|
}
|
|
8284
8334
|
} catch {
|
|
8285
8335
|
}
|
|
8286
|
-
await Promise.allSettled([...
|
|
8336
|
+
await Promise.allSettled([...served2]);
|
|
8287
8337
|
}
|
|
8288
8338
|
async serve(conn) {
|
|
8289
8339
|
this.connections.add(conn);
|
|
@@ -12015,7 +12065,12 @@ var IDENTITY_OPS = /* @__PURE__ */ new Set([
|
|
|
12015
12065
|
"chown",
|
|
12016
12066
|
"lchown"
|
|
12017
12067
|
]);
|
|
12068
|
+
var served = 0;
|
|
12069
|
+
function servedFsCalls() {
|
|
12070
|
+
return served;
|
|
12071
|
+
}
|
|
12018
12072
|
async function dispatchSyncFs(req) {
|
|
12073
|
+
served++;
|
|
12019
12074
|
const entry = resolveSyncFsToken(req.token);
|
|
12020
12075
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
12021
12076
|
if (!req.identity || !IDENTITY_OPS.has(req.op)) return dispatchOne(entry, req);
|
|
@@ -12724,6 +12779,8 @@ var SYS_TREE = {
|
|
|
12724
12779
|
slicc_wasm_memories: (listing) => `${listing.wasm?.live ?? 0} ${listing.wasm?.ceiling ?? 0} ${listing.wasm?.source ?? "none"}
|
|
12725
12780
|
`,
|
|
12726
12781
|
slicc_shared_root: (listing) => `${listing.peers ?? 0}
|
|
12782
|
+
`,
|
|
12783
|
+
slicc_fs_calls: (listing) => `${listing.fsCalls ?? 0}
|
|
12727
12784
|
`
|
|
12728
12785
|
}
|
|
12729
12786
|
};
|
|
@@ -23091,7 +23148,7 @@ async function openHostfs(spec, connect2, timing = {}) {
|
|
|
23091
23148
|
...active?.capabilities.ranges === true ? { ranges: true } : {},
|
|
23092
23149
|
...active ? { maxIo: Math.min(active.maxIo, HOSTFS_IO) } : {}
|
|
23093
23150
|
});
|
|
23094
|
-
const
|
|
23151
|
+
const served2 = serveFilesystem(
|
|
23095
23152
|
port2,
|
|
23096
23153
|
{ ...slot2.handlers, mount: ranged2 },
|
|
23097
23154
|
{
|
|
@@ -23108,12 +23165,12 @@ async function openHostfs(spec, connect2, timing = {}) {
|
|
|
23108
23165
|
up(client) {
|
|
23109
23166
|
active = timed(client, timing.callMs ?? HOSTFS_CALL_MS);
|
|
23110
23167
|
slot2.insert(client, handlers);
|
|
23111
|
-
|
|
23168
|
+
served2.invalidate(true);
|
|
23112
23169
|
},
|
|
23113
|
-
event: (event) =>
|
|
23170
|
+
event: (event) => served2.invalidate(invalidation(event)),
|
|
23114
23171
|
down() {
|
|
23115
23172
|
slot2.eject();
|
|
23116
|
-
|
|
23173
|
+
served2.invalidate(true);
|
|
23117
23174
|
},
|
|
23118
23175
|
...timing
|
|
23119
23176
|
},
|
|
@@ -25317,7 +25374,8 @@ var Launcher = class {
|
|
|
25317
25374
|
load: this.loadAverage(),
|
|
25318
25375
|
cpu: this.cpuTotal(),
|
|
25319
25376
|
wasm: this.memories.count(),
|
|
25320
|
-
peers: Number(this.locks.shared?.() === true)
|
|
25377
|
+
peers: Number(this.locks.shared?.() === true),
|
|
25378
|
+
fsCalls: servedFsCalls()
|
|
25321
25379
|
}),
|
|
25322
25380
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
25323
25381
|
crontab: (call) => this.crontab(pid, call),
|
|
@@ -25811,7 +25869,7 @@ var Launcher = class {
|
|
|
25811
25869
|
const worker = this.createDriverWorker();
|
|
25812
25870
|
const driver = new MessageChannel();
|
|
25813
25871
|
const client = new MessageChannel();
|
|
25814
|
-
const
|
|
25872
|
+
const served2 = serveClient(client.port1, {
|
|
25815
25873
|
launcher: async () => this,
|
|
25816
25874
|
scope: "transport"
|
|
25817
25875
|
});
|
|
@@ -25823,7 +25881,7 @@ var Launcher = class {
|
|
|
25823
25881
|
port: driver.port1,
|
|
25824
25882
|
dispose: () => {
|
|
25825
25883
|
worker.terminate();
|
|
25826
|
-
|
|
25884
|
+
served2.detach();
|
|
25827
25885
|
driver.port1.close();
|
|
25828
25886
|
},
|
|
25829
25887
|
onCrash: (listener) => worker.addEventListener("error", (event) => {
|
|
@@ -25837,12 +25895,12 @@ var Launcher = class {
|
|
|
25837
25895
|
const ask = (handle) => this.onMountPending?.({ target, source, ...handle ? { handle } : {} });
|
|
25838
25896
|
const { port1, port2 } = new MessageChannel();
|
|
25839
25897
|
const medium = removableMedium((handle) => {
|
|
25840
|
-
|
|
25898
|
+
served2.invalidate(true);
|
|
25841
25899
|
ask(handle);
|
|
25842
25900
|
});
|
|
25843
|
-
const
|
|
25901
|
+
const served2 = serveFilesystem(port2, medium.handlers, FSA_CAPABILITIES);
|
|
25844
25902
|
const id2 = source.slice("fsa:".length);
|
|
25845
|
-
this.removable.set(target, { id: id2, medium, served });
|
|
25903
|
+
this.removable.set(target, { id: id2, medium, served: served2 });
|
|
25846
25904
|
const stored = this.inserted.get(id2) ?? await this.media.get(id2);
|
|
25847
25905
|
if (stored && await granted(stored)) medium.insert(stored);
|
|
25848
25906
|
else ask(stored);
|
|
@@ -26853,7 +26911,7 @@ function serveKernel(port, deps) {
|
|
|
26853
26911
|
}
|
|
26854
26912
|
const { port1, port2 } = new MessageChannel();
|
|
26855
26913
|
const held = locks ? await holdLock(locks) : void 0;
|
|
26856
|
-
const
|
|
26914
|
+
const served2 = serveClient(port1, {
|
|
26857
26915
|
launcher: ready2,
|
|
26858
26916
|
signal: signalNumber,
|
|
26859
26917
|
...user !== void 0 ? { user } : {},
|
|
@@ -26861,8 +26919,8 @@ function serveKernel(port, deps) {
|
|
|
26861
26919
|
...locks ? { locks } : {},
|
|
26862
26920
|
...held ? { lock: held } : {}
|
|
26863
26921
|
});
|
|
26864
|
-
clients.add(
|
|
26865
|
-
void
|
|
26922
|
+
clients.add(served2);
|
|
26923
|
+
void served2.closed.then(() => clients.delete(served2));
|
|
26866
26924
|
return port2;
|
|
26867
26925
|
}
|
|
26868
26926
|
async function ready2() {
|