@ai-ecoverse/slicc-kernel 1.167.1 → 1.167.3
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 +91 -25
- package/dist/kernel-worker.js.map +3 -3
- 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 +91 -25
- package/dist/node.js.map +3 -3
- 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 +3 -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 +10 -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.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -8477,7 +8477,7 @@ function satisfies(version, range2) {
|
|
|
8477
8477
|
}
|
|
8478
8478
|
|
|
8479
8479
|
// src/version.ts
|
|
8480
|
-
var KERNEL_VERSION = true ? "1.167.
|
|
8480
|
+
var KERNEL_VERSION = true ? "1.167.3" : "0.0.0-dev";
|
|
8481
8481
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8482
8482
|
function procVersion() {
|
|
8483
8483
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -15242,7 +15242,7 @@ async function heldOpen(entry, path, real, req, fid) {
|
|
|
15242
15242
|
const handle = entry.hold?.(path, true, true, real, void 0, req.ino, fid);
|
|
15243
15243
|
if (handle === void 0) return null;
|
|
15244
15244
|
await entry.announceHold?.(real ?? path, fid, ((req.mode ?? 0) & HOLD_CREATED) !== 0);
|
|
15245
|
-
return handle;
|
|
15245
|
+
return real !== void 0 && real !== path ? { handle, real } : handle;
|
|
15246
15246
|
}
|
|
15247
15247
|
async function idOf(entry, path) {
|
|
15248
15248
|
return await entry.fileId?.(path)?.catch(() => void 0) ?? void 0;
|
|
@@ -16203,6 +16203,10 @@ var AsyncRangedFile = class {
|
|
|
16203
16203
|
truncate(size) {
|
|
16204
16204
|
this.pages.truncate(size);
|
|
16205
16205
|
}
|
|
16206
|
+
wrote(at, length2) {
|
|
16207
|
+
this.pages.landed(at, length2);
|
|
16208
|
+
this.version = void 0;
|
|
16209
|
+
}
|
|
16206
16210
|
async flush() {
|
|
16207
16211
|
if (this.pinned || !this.pages.changed) return;
|
|
16208
16212
|
const plan = this.pages.plan();
|
|
@@ -24488,6 +24492,7 @@ var VfsNode = class {
|
|
|
24488
24492
|
ino;
|
|
24489
24493
|
onDirty;
|
|
24490
24494
|
onDirtyState;
|
|
24495
|
+
crowded;
|
|
24491
24496
|
counted = false;
|
|
24492
24497
|
recount() {
|
|
24493
24498
|
if (!this.onDirtyState) return;
|
|
@@ -24605,9 +24610,48 @@ var VfsNode = class {
|
|
|
24605
24610
|
if (!this.dirty) this.data = void 0;
|
|
24606
24611
|
}
|
|
24607
24612
|
async size() {
|
|
24613
|
+
if (this.peerHeld()) {
|
|
24614
|
+
const known = await this.exact();
|
|
24615
|
+
if (known !== void 0) return known;
|
|
24616
|
+
}
|
|
24608
24617
|
const ranged2 = await this.prepare();
|
|
24609
24618
|
return ranged2 ? ranged2.size() : this.length;
|
|
24610
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
|
+
}
|
|
24611
24655
|
async pread(max, at) {
|
|
24612
24656
|
const ranged2 = await this.prepare();
|
|
24613
24657
|
if (ranged2) return this.followed(() => ranged2.read(at, max));
|
|
@@ -24679,6 +24723,13 @@ var VfsNode = class {
|
|
|
24679
24723
|
if (bytes.length === 0) return 0;
|
|
24680
24724
|
await this.settled();
|
|
24681
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
|
+
}
|
|
24682
24733
|
const ranged2 = await this.prepare();
|
|
24683
24734
|
if (ranged2) {
|
|
24684
24735
|
await this.followed(() => ranged2.write(at, bytes));
|
|
@@ -24775,6 +24826,7 @@ var VfsNodes = class {
|
|
|
24775
24826
|
movedAway;
|
|
24776
24827
|
opened;
|
|
24777
24828
|
dirtied;
|
|
24829
|
+
crowded;
|
|
24778
24830
|
fs;
|
|
24779
24831
|
entries;
|
|
24780
24832
|
onClosed;
|
|
@@ -24920,6 +24972,7 @@ var VfsNodes = class {
|
|
|
24920
24972
|
this.reshaped();
|
|
24921
24973
|
node.onDirty = () => this.changed?.(node.path);
|
|
24922
24974
|
node.onDirtyState = (on) => this.dirtied?.(on ? 1 : -1);
|
|
24975
|
+
node.crowded = (at) => this.crowded?.(at) === true;
|
|
24923
24976
|
node.recount();
|
|
24924
24977
|
this.byPath.set(path, node);
|
|
24925
24978
|
if (id2 !== void 0) this.know(node, id2);
|
|
@@ -31488,6 +31541,7 @@ var Launcher = class {
|
|
|
31488
31541
|
hostname;
|
|
31489
31542
|
watchers = new FsWatchers();
|
|
31490
31543
|
changes = new FileChanges();
|
|
31544
|
+
peerHeldPaths = /* @__PURE__ */ new Set();
|
|
31491
31545
|
renameLog = new RenameLog(this.changes);
|
|
31492
31546
|
cwds = new CwdTable(async (path) => {
|
|
31493
31547
|
const { dev, ino } = await this.base.stat(path);
|
|
@@ -33701,7 +33755,7 @@ var Launcher = class {
|
|
|
33701
33755
|
kept: async (tab, items, paths) => {
|
|
33702
33756
|
for (const { path, hidden, fid } of items) {
|
|
33703
33757
|
if (hidden !== void 0 && sharedKept(hidden)) await this.adoptShared(path, hidden, fid);
|
|
33704
|
-
else this.adoptKept(path, hidden, fid, ours);
|
|
33758
|
+
else await this.adoptKept(path, hidden, fid, ours);
|
|
33705
33759
|
}
|
|
33706
33760
|
await open2(tab, paths);
|
|
33707
33761
|
},
|
|
@@ -33711,14 +33765,33 @@ var Launcher = class {
|
|
|
33711
33765
|
}
|
|
33712
33766
|
};
|
|
33713
33767
|
}
|
|
33714
|
-
async
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33768
|
+
async keptIds(hidden, fid) {
|
|
33769
|
+
if (fid !== void 0) return [fid];
|
|
33770
|
+
const st = await this.rootFs.lstat(hidden).catch(() => void 0);
|
|
33771
|
+
return st?.fid === void 0 ? [void 0] : [void 0, st.fid];
|
|
33772
|
+
}
|
|
33773
|
+
boundTo(path, hidden, fids) {
|
|
33774
|
+
const keeping = [...this.held].map(
|
|
33775
|
+
(holds2) => [
|
|
33776
|
+
holds2,
|
|
33777
|
+
fids.flatMap((fid) => [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)])
|
|
33778
|
+
]
|
|
33779
|
+
).filter(([, ids]) => ids.length > 0);
|
|
33719
33780
|
const nodes = [...this.openFiles].filter(
|
|
33720
33781
|
(open2) => [path, hidden].some((at) => open2.holdsAt(at))
|
|
33721
33782
|
);
|
|
33783
|
+
const keep2 = (file) => {
|
|
33784
|
+
const kept = nodes.filter(
|
|
33785
|
+
(open2) => fids.some((fid) => open2.keepBound([hidden, path], fid, file))
|
|
33786
|
+
).length;
|
|
33787
|
+
for (let i = kept; i < nodes.length; i++) file.release();
|
|
33788
|
+
for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
|
|
33789
|
+
void this.rootFs.announceHolds?.([], [path, hidden], false);
|
|
33790
|
+
};
|
|
33791
|
+
return { keeping, nodes, keep: keep2 };
|
|
33792
|
+
}
|
|
33793
|
+
async adoptShared(path, hidden, fid) {
|
|
33794
|
+
const { keeping, nodes, keep: keep2 } = this.boundTo(path, hidden, await this.keptIds(hidden, fid));
|
|
33722
33795
|
const refs = keeping.length + nodes.length;
|
|
33723
33796
|
if (refs === 0) {
|
|
33724
33797
|
this.held.doubt([path]);
|
|
@@ -33733,33 +33806,22 @@ var Launcher = class {
|
|
|
33733
33806
|
this.held.doubt([path]);
|
|
33734
33807
|
return;
|
|
33735
33808
|
}
|
|
33736
|
-
|
|
33737
|
-
for (let i = kept; i < nodes.length; i++) file.release();
|
|
33738
|
-
for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
|
|
33739
|
-
void this.rootFs.announceHolds?.([], [path, hidden], false);
|
|
33809
|
+
keep2(file);
|
|
33740
33810
|
}
|
|
33741
|
-
adoptKept(path, hidden, fid, ours) {
|
|
33811
|
+
async adoptKept(path, hidden, fid, ours) {
|
|
33742
33812
|
if (hidden === void 0 || !ours(hidden)) {
|
|
33743
33813
|
this.held.doubt([path]);
|
|
33744
33814
|
return;
|
|
33745
33815
|
}
|
|
33746
|
-
const
|
|
33747
|
-
(holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
|
|
33748
|
-
);
|
|
33749
|
-
const keeping = bound3.filter(([, ids]) => ids.length > 0);
|
|
33750
|
-
const nodes = [...this.openFiles].filter(
|
|
33751
|
-
(open2) => [path, hidden].some((at) => open2.holdsAt(at))
|
|
33752
|
-
);
|
|
33816
|
+
const { keeping, nodes, keep: keep2 } = this.boundTo(path, hidden, await this.keptIds(hidden, fid));
|
|
33753
33817
|
const file = this.unlinked.adopt(hidden, Math.max(keeping.length + nodes.length, 1));
|
|
33754
|
-
const kept = nodes.filter((open2) => open2.keepBound([hidden, path], fid, file)).length;
|
|
33755
|
-
for (let i = kept; i < nodes.length; i++) file.release();
|
|
33756
33818
|
if (keeping.length === 0 && nodes.length === 0) file.release();
|
|
33757
|
-
|
|
33758
|
-
void this.rootFs.announceHolds?.([], [path, hidden], false);
|
|
33819
|
+
keep2(file);
|
|
33759
33820
|
}
|
|
33760
33821
|
openNodes(fs, entries) {
|
|
33761
33822
|
const nodes = new VfsNodes(fs, (path) => this.released(path), entries);
|
|
33762
33823
|
nodes.opened = (node) => this.nodeOpened(node);
|
|
33824
|
+
nodes.crowded = (path) => this.peerHeldPaths.has(path);
|
|
33763
33825
|
return nodes;
|
|
33764
33826
|
}
|
|
33765
33827
|
nodeOpened(node) {
|
|
@@ -33781,13 +33843,17 @@ var Launcher = class {
|
|
|
33781
33843
|
return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
|
|
33782
33844
|
}
|
|
33783
33845
|
flushingPeers(fs) {
|
|
33784
|
-
fs.peerHolds?.((paths) =>
|
|
33846
|
+
fs.peerHolds?.((paths) => {
|
|
33847
|
+
this.peerHeldPaths = new Set(paths);
|
|
33848
|
+
this.changes.peerHeld(paths);
|
|
33849
|
+
});
|
|
33785
33850
|
const peers = fs.peers?.();
|
|
33786
33851
|
if (!peers) return void 0;
|
|
33787
33852
|
peers.watch((crowded) => {
|
|
33788
33853
|
this.changes.flush();
|
|
33789
33854
|
this.changes.foreign(crowded);
|
|
33790
33855
|
if (crowded) {
|
|
33856
|
+
void this.announceHeld();
|
|
33791
33857
|
void this.held.bindIds((path) => this.rootFileId(path)).then(
|
|
33792
33858
|
() => Promise.all([...this.openFiles].map((open2) => open2.bindIds((p) => this.rootFileId(p))))
|
|
33793
33859
|
).then(() => this.announceHeld());
|