@ai-ecoverse/slicc-kernel 1.167.0 → 1.167.2
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 +2 -2
- package/dist/kernel-worker.js +70 -3
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.cjs +34 -12
- package/dist/node-process-worker.cjs.map +3 -3
- package/dist/node-process-worker.js +34 -12
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +70 -3
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +34 -12
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/ranged.d.ts +1 -0
- package/dist/types/kernel/vfs-file.d.ts +6 -0
- package/dist/types/launcher.d.ts +1 -0
- package/dist/types/process/wasi/wasi-files.d.ts +3 -0
- package/dist/types/realm/sync-fs-wire.d.ts +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -858,7 +858,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
858
858
|
- **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.
|
|
859
859
|
- **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.
|
|
860
860
|
- **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.
|
|
861
|
-
- **Appends across tabs** ([#1520](https://github.com/ai-ecoverse/slicc-kernel/issues/1520)): while another kernel shares the OPFS root, an `O_APPEND` write lands at the file's real end under a per-file Web Lock, so two tabs' appends never overwrite each other. A tab that sees another join flushes its buffered appends and waits for the writes it has under way at that moment (later ones don't hold it) before it answers, and the joiner's appends wait for every such answer pending at the time, without a timeout, as long as the other tab lives; an append already under way when a further tab's note arrives isn't held back ([#1546](https://github.com/ai-ecoverse/slicc-kernel/issues/1546)). Alone, an append is buffered as before.
|
|
861
|
+
- **Appends across tabs** ([#1520](https://github.com/ai-ecoverse/slicc-kernel/issues/1520)): while another kernel shares the OPFS root, an `O_APPEND` write lands at the file's real end under a per-file Web Lock, so two tabs' appends never overwrite each other. A tab that sees another join flushes its buffered appends and waits for the writes it has under way at that moment (later ones don't hold it) before it answers, and the joiner's appends wait for every such answer pending at the time, without a timeout, as long as the other tab lives; an append already under way when a further tab's note arrives isn't held back ([#1546](https://github.com/ai-ecoverse/slicc-kernel/issues/1546)). Alone, an append is buffered as before. While another tab holds a file open, a seek to its end or an `fstat` reads the real end, and a write past that end goes through under the same lock before it returns, so a write at the end *without* `O_APPEND` (a seek to the end, then a write) doesn't overwrite another tab's appends either ([#1546](https://github.com/ai-ecoverse/slicc-kernel/issues/1546)). That holds for a WASI program's own files, for descriptors the kernel holds (a JS program's `ctx.sync.open`, a shell's `exec 3<>` once a child has it), and for a file opened through a symlink, which is looked up by its real path ([#1675](https://github.com/ai-ecoverse/slicc-kernel/issues/1675)). A file no other tab holds stays buffered: no lock and no extra call. **Limits:** a write inside the file stays buffered, so it's close-to-open across tabs, as on NFS. Reading to the end, rather than seeking there, still stops at the end this tab last knew. A WASI program that opened a file through a symlink keeps the real path it had at open, so after the target is renamed its writes stay buffered until it opens the file again. A writer that has seen the file held keeps reading its end until it finds the file no longer held, so a peer that appends and closes right away is covered; a peer whose whole open, append and close fall between two of this tab's looks is seen only through its notices.
|
|
862
862
|
- **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 Emscripten program's file goes to a kernel descriptor at its first lock. From then on, its `fstat` size is that descriptor's, so on a file that isn't read and written in pages (a root outside OPFS, or `hostfs` without `ranges`), sqlite sees the pages it wrote and the kernel still holds (#1019). 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` (wasi-pnpm's store-operation lock) is a whole-file lock, per process and keyed by path, while the kernel is alone on its root. While another kernel shares the root, it becomes a non-blocking `flock` on the descriptor's open file instead, keyed by the file's identity, so it excludes across kernels and tabs (#983). It's released on `unlock`, on close, and about a second after the holder's tab closes. A tryLock checks both the local table and the flock table, so a lock taken before a peer joins, or while one was there, still excludes after the switch. 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.
|
|
863
863
|
- **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.
|
|
864
864
|
- **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()` is a stub compiled into its libc that never reaches the kernel either, until a homescoop shim routes it to a kernel import ([#680](https://github.com/ai-ecoverse/slicc-kernel/issues/680)). 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.
|
|
@@ -1256,7 +1256,7 @@ The database is `slicc-kernel` (or the `metadata` option), schema version 1, wit
|
|
|
1256
1256
|
|
|
1257
1257
|
Not covered: hard links (OPFS cannot share contents between names; they work only within a mount whose driver offers `links`, such as `tmpfs`). File owners are stored only with `users: 'report'` or `'enforce'` (see [File owners](#file-owners)). With `users: 'off'`, an Emscripten process built against the kernel's credential calls (its glue imports `slicc_cred_js`, as homescoop's H1 builds do) sees every file as owned by its own effective uid and gid: root's 0 by default, a user's own ids when it runs as one, and the new ids after a `setuid`. Owner checks such as git's `safe.directory`, ssh's key checks and gnupg's home directory check then pass, as WASIX programs already see through their libc. An older program, whose libc says it is uid 1000 whoever runs it, sees every file as owned by 1000:1000 as before, so its own checks pass too. The kernel decides this once, when it loads the program. Emscripten also doesn't enforce permission bits, so a file runs whether or not its exec bit is set; the bits are what `ls`, `stat` and `test` report. With `users: 'off'` a file or directory with no permission bits at all shows as `0666` or `0777` to Emscripten programs; with owners on they see the real mode (`0`), and the kernel, not Emscripten's own checks, decides who may read, write or enter it (`cd` asks the kernel for `x` on the directory), so root still enters a `000` directory.
|
|
1258
1258
|
|
|
1259
|
-
Reads retry briefly when a concurrent write has invalidated the OPFS file snapshot (`NotReadableError`).
|
|
1259
|
+
Reads retry briefly when a concurrent write has invalidated the OPFS file snapshot (`NotReadableError`). A positioned read uses the snapshot the kernel's last look at the file took (below) when it is still current and covers the whole range, so a run of reads through a descriptor makes no `getFile` of its own beyond the one each window's look takes ([#1626](https://github.com/ai-ecoverse/slicc-kernel/issues/1626)). A read past the snapshot's end, an empty read, or a snapshot that can't be read any more takes a fresh one. In Chrome the snapshot holds no bytes of its own: it reads the file as it is now, so a write in place (a sync access handle) shows at once, and a file another writer replaced, truncated or removed fails the snapshot (`NotReadableError`, `NotFoundError`), which sends the read to a fresh one. Holding a snapshot blocks no other writer: a sync access handle, `createWritable`, `move` and `removeEntry` all go ahead.
|
|
1260
1260
|
|
|
1261
1261
|
At boot, a kernel drops sidecar records whose file is gone (and symlink records whose name became a real file or whose directory is gone), left behind by a tab that closed mid-change. It does this only when it is the only kernel on the origin (the watch channel's join handshake finds no other), and it drops a record only if it is still the one it found: another tab's live changes made while it looked, such as a placeholder tar replaces with a symlink, are kept. Up to 1.119.2, a tab that booted while another extracted symlinks with tar could drop some of them.
|
|
1262
1262
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -2556,6 +2556,15 @@ var OpfsFs = class {
|
|
|
2556
2556
|
}
|
|
2557
2557
|
async pread(path, offset, length2) {
|
|
2558
2558
|
return this.reading(path, async (handle) => {
|
|
2559
|
+
const seen = this.glanced(handle);
|
|
2560
|
+
if (seen && length2 > 0 && offset + length2 <= seen.size) {
|
|
2561
|
+
const bytes = await this.io(seen.slice(offset, offset + length2).arrayBuffer()).then(
|
|
2562
|
+
(buffer) => new Uint8Array(buffer),
|
|
2563
|
+
() => void 0
|
|
2564
|
+
);
|
|
2565
|
+
if (bytes?.length === length2) return bytes;
|
|
2566
|
+
this.glances.delete(handle);
|
|
2567
|
+
}
|
|
2559
2568
|
const file = await this.io(handle.getFile());
|
|
2560
2569
|
await this.refuseNode(path, file.size === 0);
|
|
2561
2570
|
return new Uint8Array(await file.slice(offset, offset + length2).arrayBuffer());
|
|
@@ -8468,7 +8477,7 @@ function satisfies(version, range2) {
|
|
|
8468
8477
|
}
|
|
8469
8478
|
|
|
8470
8479
|
// src/version.ts
|
|
8471
|
-
var KERNEL_VERSION = true ? "1.167.
|
|
8480
|
+
var KERNEL_VERSION = true ? "1.167.2" : "0.0.0-dev";
|
|
8472
8481
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8473
8482
|
function procVersion() {
|
|
8474
8483
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -15233,7 +15242,7 @@ async function heldOpen(entry, path, real, req, fid) {
|
|
|
15233
15242
|
const handle = entry.hold?.(path, true, true, real, void 0, req.ino, fid);
|
|
15234
15243
|
if (handle === void 0) return null;
|
|
15235
15244
|
await entry.announceHold?.(real ?? path, fid, ((req.mode ?? 0) & HOLD_CREATED) !== 0);
|
|
15236
|
-
return handle;
|
|
15245
|
+
return real !== void 0 && real !== path ? { handle, real } : handle;
|
|
15237
15246
|
}
|
|
15238
15247
|
async function idOf(entry, path) {
|
|
15239
15248
|
return await entry.fileId?.(path)?.catch(() => void 0) ?? void 0;
|
|
@@ -16194,6 +16203,10 @@ var AsyncRangedFile = class {
|
|
|
16194
16203
|
truncate(size) {
|
|
16195
16204
|
this.pages.truncate(size);
|
|
16196
16205
|
}
|
|
16206
|
+
wrote(at, length2) {
|
|
16207
|
+
this.pages.landed(at, length2);
|
|
16208
|
+
this.version = void 0;
|
|
16209
|
+
}
|
|
16197
16210
|
async flush() {
|
|
16198
16211
|
if (this.pinned || !this.pages.changed) return;
|
|
16199
16212
|
const plan = this.pages.plan();
|
|
@@ -24479,6 +24492,7 @@ var VfsNode = class {
|
|
|
24479
24492
|
ino;
|
|
24480
24493
|
onDirty;
|
|
24481
24494
|
onDirtyState;
|
|
24495
|
+
crowded;
|
|
24482
24496
|
counted = false;
|
|
24483
24497
|
recount() {
|
|
24484
24498
|
if (!this.onDirtyState) return;
|
|
@@ -24596,9 +24610,48 @@ var VfsNode = class {
|
|
|
24596
24610
|
if (!this.dirty) this.data = void 0;
|
|
24597
24611
|
}
|
|
24598
24612
|
async size() {
|
|
24613
|
+
if (this.peerHeld()) {
|
|
24614
|
+
const known = await this.exact();
|
|
24615
|
+
if (known !== void 0) return known;
|
|
24616
|
+
}
|
|
24599
24617
|
const ranged2 = await this.prepare();
|
|
24600
24618
|
return ranged2 ? ranged2.size() : this.length;
|
|
24601
24619
|
}
|
|
24620
|
+
peered = false;
|
|
24621
|
+
peerHeld() {
|
|
24622
|
+
if (this.orphaned || this.revoked || this.pin) return false;
|
|
24623
|
+
if (this.crowded?.(this.path) === true) this.peered = true;
|
|
24624
|
+
return this.peered;
|
|
24625
|
+
}
|
|
24626
|
+
async exact() {
|
|
24627
|
+
await this.flush();
|
|
24628
|
+
const { stat } = this.fs;
|
|
24629
|
+
const io = rangedOps(this.fs);
|
|
24630
|
+
if (!stat || !io) return void 0;
|
|
24631
|
+
const st = await this.followed(() => stat.call(this.fs, this.path));
|
|
24632
|
+
this.peered = this.crowded?.(this.path) === true;
|
|
24633
|
+
const size = st.size ?? 0;
|
|
24634
|
+
if (this.ranged) {
|
|
24635
|
+
if (size !== this.ranged.size()) {
|
|
24636
|
+
this.ranged = new AsyncRangedFile(io, this.path, size, st.version);
|
|
24637
|
+
}
|
|
24638
|
+
return void 0;
|
|
24639
|
+
}
|
|
24640
|
+
if (this.data && size !== this.length) this.data = void 0;
|
|
24641
|
+
return this.data ? void 0 : size;
|
|
24642
|
+
}
|
|
24643
|
+
landed(at, bytes) {
|
|
24644
|
+
if (this.ranged) {
|
|
24645
|
+
this.ranged.wrote(at, bytes.length);
|
|
24646
|
+
return;
|
|
24647
|
+
}
|
|
24648
|
+
if (!this.data) return;
|
|
24649
|
+
const end = at + bytes.length;
|
|
24650
|
+
const buf = this.ensure(end);
|
|
24651
|
+
if (at > this.length) buf.fill(0, this.length, at);
|
|
24652
|
+
buf.set(bytes, at);
|
|
24653
|
+
this.length = Math.max(this.length, end);
|
|
24654
|
+
}
|
|
24602
24655
|
async pread(max, at) {
|
|
24603
24656
|
const ranged2 = await this.prepare();
|
|
24604
24657
|
if (ranged2) return this.followed(() => ranged2.read(at, max));
|
|
@@ -24670,6 +24723,13 @@ var VfsNode = class {
|
|
|
24670
24723
|
if (bytes.length === 0) return 0;
|
|
24671
24724
|
await this.settled();
|
|
24672
24725
|
await this.fits(at + bytes.length);
|
|
24726
|
+
const io = this.peerHeld() ? rangedOps(this.fs) : void 0;
|
|
24727
|
+
if (io && at + bytes.length > await this.size()) {
|
|
24728
|
+
const copy = bytes.slice();
|
|
24729
|
+
await this.followed(() => io.pwrite(this.path, at, copy, true));
|
|
24730
|
+
this.landed(at, bytes);
|
|
24731
|
+
return bytes.length;
|
|
24732
|
+
}
|
|
24673
24733
|
const ranged2 = await this.prepare();
|
|
24674
24734
|
if (ranged2) {
|
|
24675
24735
|
await this.followed(() => ranged2.write(at, bytes));
|
|
@@ -24766,6 +24826,7 @@ var VfsNodes = class {
|
|
|
24766
24826
|
movedAway;
|
|
24767
24827
|
opened;
|
|
24768
24828
|
dirtied;
|
|
24829
|
+
crowded;
|
|
24769
24830
|
fs;
|
|
24770
24831
|
entries;
|
|
24771
24832
|
onClosed;
|
|
@@ -24911,6 +24972,7 @@ var VfsNodes = class {
|
|
|
24911
24972
|
this.reshaped();
|
|
24912
24973
|
node.onDirty = () => this.changed?.(node.path);
|
|
24913
24974
|
node.onDirtyState = (on) => this.dirtied?.(on ? 1 : -1);
|
|
24975
|
+
node.crowded = (at) => this.crowded?.(at) === true;
|
|
24914
24976
|
node.recount();
|
|
24915
24977
|
this.byPath.set(path, node);
|
|
24916
24978
|
if (id2 !== void 0) this.know(node, id2);
|
|
@@ -31479,6 +31541,7 @@ var Launcher = class {
|
|
|
31479
31541
|
hostname;
|
|
31480
31542
|
watchers = new FsWatchers();
|
|
31481
31543
|
changes = new FileChanges();
|
|
31544
|
+
peerHeldPaths = /* @__PURE__ */ new Set();
|
|
31482
31545
|
renameLog = new RenameLog(this.changes);
|
|
31483
31546
|
cwds = new CwdTable(async (path) => {
|
|
31484
31547
|
const { dev, ino } = await this.base.stat(path);
|
|
@@ -33751,6 +33814,7 @@ var Launcher = class {
|
|
|
33751
33814
|
openNodes(fs, entries) {
|
|
33752
33815
|
const nodes = new VfsNodes(fs, (path) => this.released(path), entries);
|
|
33753
33816
|
nodes.opened = (node) => this.nodeOpened(node);
|
|
33817
|
+
nodes.crowded = (path) => this.peerHeldPaths.has(path);
|
|
33754
33818
|
return nodes;
|
|
33755
33819
|
}
|
|
33756
33820
|
nodeOpened(node) {
|
|
@@ -33772,7 +33836,10 @@ var Launcher = class {
|
|
|
33772
33836
|
return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
|
|
33773
33837
|
}
|
|
33774
33838
|
flushingPeers(fs) {
|
|
33775
|
-
fs.peerHolds?.((paths) =>
|
|
33839
|
+
fs.peerHolds?.((paths) => {
|
|
33840
|
+
this.peerHeldPaths = new Set(paths);
|
|
33841
|
+
this.changes.peerHeld(paths);
|
|
33842
|
+
});
|
|
33776
33843
|
const peers = fs.peers?.();
|
|
33777
33844
|
if (!peers) return void 0;
|
|
33778
33845
|
peers.watch((crowded) => {
|