@ai-ecoverse/slicc-kernel 1.39.1 → 1.39.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 +1 -1
- package/dist/kernel-worker.js +101 -26
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +56 -7
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +290 -201
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +56 -7
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -420,7 +420,7 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
|
|
|
420
420
|
|
|
421
421
|
### Exec
|
|
422
422
|
|
|
423
|
-
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux
|
|
423
|
+
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux, whether the descriptors live in the kernel or a program opened the file in its own worker: every open, read-only ones included, is known to the kernel, and all holders share the one file (writes through one are seen through the others). The kernel does not copy it: as NFS does with its "silly rename", it moves the file into a hidden directory, `.slicc-unlinked/`, at the root of the mount it lives on (or of the root file system), and serves the holders' reads and writes from there (a worker's I/O on the old path while the path is gone). No file data is held in memory for it. The file is deleted when the last holder closes it or exits, and the directory goes as soon as it is empty. A file created at the path afterwards is the new file for everyone, also for the holder; a holder whose file another process renames (rather than removes) still uses the old path. Listings in the kernel leave `.slicc-unlinked` out, but on an `fsa` or `hostfs` mount it is visible on the real machine while a removed file is held (add `.slicc-unlinked/` to a `.gitignore` there). Each kernel names its entries after itself and holds a Web Lock for that name while it runs; when a mount is set up (and the root at start), the kernel removes the entries whose owner holds no lock, so a tab that died while holding a file leaves nothing behind for long, and entries of other live tabs on the same origin are left alone. Under the Node entry, which has no Web Locks, entries are named after the kernel's process id instead, and an entry is removed when that process is gone (`kill(pid, 0)` fails with `ESRCH`); a reused pid leaves the entry alone. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
|
|
424
424
|
|
|
425
425
|
- **Emscripten:** `Module.sliccKernel.execve(file, argv, env, cwd)` runs `file` as this process's new image on its fds 0, 1 and 2, waits for it, and returns its wait status, or a negative errno when it cannot start. `env` and `cwd` may be `null` for this process's own. An exec shim detects it with `typeof Module.sliccKernel.execve === 'function'` and exits with the status it returns, as `execve` never returns on success. Older shims spawn the program and then call `Module.sliccKernel.execWait(pid)`; that works the same way: a new program asks the kernel for its pid before `main`, and the kernel answers once its parent has made its next call, so an image an older shim execs also reports the pid it replaced (the wait starts once the program is loaded; should the parent make no call within 100 ms after that, the kernel answers anyway, and the image may then report its own pid; `launcher.identities` counts the programs that asked and those answered by that timeout).
|
|
426
426
|
- **WASIX:** `proc_exec` does this by itself.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -5315,16 +5315,20 @@ var UnlinkedKeeper = class {
|
|
|
5315
5315
|
locks;
|
|
5316
5316
|
busy;
|
|
5317
5317
|
waiting = /* @__PURE__ */ new Set();
|
|
5318
|
-
|
|
5318
|
+
owner;
|
|
5319
|
+
instance;
|
|
5319
5320
|
gate = new PathGate();
|
|
5320
5321
|
queue = Promise.resolve();
|
|
5321
5322
|
count = 0;
|
|
5322
|
-
constructor(fs, holders, rootOf,
|
|
5323
|
+
constructor(fs, holders, rootOf, options = {}) {
|
|
5324
|
+
const { locks, owner } = options;
|
|
5323
5325
|
this.fs = fs;
|
|
5324
5326
|
this.holders = holders;
|
|
5325
5327
|
this.rootOf = rootOf;
|
|
5326
5328
|
this.locks = locks;
|
|
5327
|
-
this.busy = busy;
|
|
5329
|
+
this.busy = options.busy ?? (() => false);
|
|
5330
|
+
this.owner = owner;
|
|
5331
|
+
this.instance = owner ? `${owner.id}~${crypto.randomUUID()}` : crypto.randomUUID();
|
|
5328
5332
|
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
5329
5333
|
}
|
|
5330
5334
|
serial(op) {
|
|
@@ -5332,8 +5336,7 @@ var UnlinkedKeeper = class {
|
|
|
5332
5336
|
this.queue = next.catch(() => void 0);
|
|
5333
5337
|
return next;
|
|
5334
5338
|
}
|
|
5335
|
-
wrap() {
|
|
5336
|
-
const fs = this.fs;
|
|
5339
|
+
wrap(fs = this.fs) {
|
|
5337
5340
|
const keeper = this;
|
|
5338
5341
|
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
5339
5342
|
return {
|
|
@@ -5345,7 +5348,9 @@ var UnlinkedKeeper = class {
|
|
|
5345
5348
|
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
5346
5349
|
},
|
|
5347
5350
|
async rename(from, to) {
|
|
5348
|
-
const
|
|
5351
|
+
const target = fs.resolvePath("/", to);
|
|
5352
|
+
if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
|
|
5353
|
+
const pinned = await keeper.pin(target);
|
|
5349
5354
|
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
5350
5355
|
},
|
|
5351
5356
|
readdir: async (path) => visible(await fs.readdir(path)),
|
|
@@ -5365,8 +5370,8 @@ var UnlinkedKeeper = class {
|
|
|
5365
5370
|
const held = /* @__PURE__ */ new Map();
|
|
5366
5371
|
for (const holder of this.holders) {
|
|
5367
5372
|
if (holder.owns(target)) continue;
|
|
5368
|
-
for (const path of holder.
|
|
5369
|
-
if (!within3(path, target) || holder.
|
|
5373
|
+
for (const path of holder.held()) {
|
|
5374
|
+
if (!within3(path, target) || holder.isKept(path)) continue;
|
|
5370
5375
|
held.set(path, [...held.get(path) ?? [], holder]);
|
|
5371
5376
|
}
|
|
5372
5377
|
}
|
|
@@ -5381,21 +5386,25 @@ var UnlinkedKeeper = class {
|
|
|
5381
5386
|
const done4 = () => {
|
|
5382
5387
|
for (const release of releases) release();
|
|
5383
5388
|
};
|
|
5389
|
+
const files = /* @__PURE__ */ new Map();
|
|
5390
|
+
for (const [path, at] of hidden) {
|
|
5391
|
+
const holders = held.get(path);
|
|
5392
|
+
const file = this.keptFile(at, { refs: holders.length + 1 });
|
|
5393
|
+
files.set(path, file);
|
|
5394
|
+
for (const holder of holders) holder.keep(path, file);
|
|
5395
|
+
}
|
|
5384
5396
|
return {
|
|
5385
5397
|
moved: new Set(hidden.keys()),
|
|
5386
5398
|
commit: () => {
|
|
5387
|
-
for (const
|
|
5388
|
-
const holders = held.get(path);
|
|
5389
|
-
const file = this.keptFile(at, { refs: holders.length });
|
|
5390
|
-
for (const holder of holders) holder.unlinked.set(path, file);
|
|
5391
|
-
}
|
|
5399
|
+
for (const file of files.values()) file.release();
|
|
5392
5400
|
done4();
|
|
5393
5401
|
},
|
|
5394
5402
|
rollback: async () => {
|
|
5395
|
-
for (const [path,
|
|
5403
|
+
for (const [path, file] of files) {
|
|
5404
|
+
for (const holder of held.get(path)) holder.unkeep(path, file);
|
|
5396
5405
|
await this.serial(async () => {
|
|
5397
|
-
await Promise.allSettled([this.fs.rename(
|
|
5398
|
-
await this.tidy(parentOf3(
|
|
5406
|
+
await Promise.allSettled([this.fs.rename(file.hidden, path)]);
|
|
5407
|
+
await this.tidy(parentOf3(file.hidden));
|
|
5399
5408
|
});
|
|
5400
5409
|
}
|
|
5401
5410
|
done4();
|
|
@@ -5441,16 +5450,18 @@ var UnlinkedKeeper = class {
|
|
|
5441
5450
|
}
|
|
5442
5451
|
sweep(root) {
|
|
5443
5452
|
const query = this.locks?.query?.bind(this.locks);
|
|
5444
|
-
|
|
5453
|
+
const owner = this.owner;
|
|
5454
|
+
if (!query && !owner) return Promise.resolve();
|
|
5445
5455
|
return this.serial(async () => {
|
|
5446
5456
|
const dir = dirOf(root);
|
|
5447
5457
|
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
5448
5458
|
if (!names) return;
|
|
5449
|
-
const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
5459
|
+
const held = query && new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
5450
5460
|
for (const name2 of names) {
|
|
5451
|
-
const
|
|
5452
|
-
|
|
5453
|
-
|
|
5461
|
+
const id = name2.slice(0, name2.lastIndexOf("."));
|
|
5462
|
+
const at = id.indexOf("~");
|
|
5463
|
+
const dead = id !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id)) : owner?.alive(id.slice(0, at)) === false);
|
|
5464
|
+
if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
5454
5465
|
}
|
|
5455
5466
|
await this.tidy(dir);
|
|
5456
5467
|
});
|
|
@@ -5884,6 +5895,18 @@ var HeldPaths = class extends Map {
|
|
|
5884
5895
|
holding(path) {
|
|
5885
5896
|
return this.opens.has(this.keyOf(path));
|
|
5886
5897
|
}
|
|
5898
|
+
held() {
|
|
5899
|
+
return this.opens.keys();
|
|
5900
|
+
}
|
|
5901
|
+
isKept(path) {
|
|
5902
|
+
return this.unlinked.has(path);
|
|
5903
|
+
}
|
|
5904
|
+
keep(path, file) {
|
|
5905
|
+
this.unlinked.set(path, file);
|
|
5906
|
+
}
|
|
5907
|
+
unkeep(path) {
|
|
5908
|
+
this.unlinked.delete(path);
|
|
5909
|
+
}
|
|
5887
5910
|
hold(path, on, open = false, real) {
|
|
5888
5911
|
if (open) {
|
|
5889
5912
|
const key = real ?? this.keyOf(path);
|
|
@@ -6751,6 +6774,9 @@ async function run(entry, path, req) {
|
|
|
6751
6774
|
case "symlink":
|
|
6752
6775
|
await fs.symlink(req.arg2 ?? "", path);
|
|
6753
6776
|
return done;
|
|
6777
|
+
case "create":
|
|
6778
|
+
await createExclusive(fs, path);
|
|
6779
|
+
return done;
|
|
6754
6780
|
case "readlink":
|
|
6755
6781
|
return json2(await fs.readlink(path));
|
|
6756
6782
|
case "chmod":
|
|
@@ -6770,6 +6796,15 @@ async function run(entry, path, req) {
|
|
|
6770
6796
|
return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
|
|
6771
6797
|
}
|
|
6772
6798
|
}
|
|
6799
|
+
async function createExclusive(fs, path) {
|
|
6800
|
+
if (fs.createExclusive) return fs.createExclusive(path);
|
|
6801
|
+
if (await fs.lstat(path).then(
|
|
6802
|
+
() => true,
|
|
6803
|
+
() => false
|
|
6804
|
+
))
|
|
6805
|
+
throw syncError("EEXIST", path);
|
|
6806
|
+
await fs.writeFile(path, new Uint8Array(0));
|
|
6807
|
+
}
|
|
6773
6808
|
async function ranged(fs, path, req) {
|
|
6774
6809
|
const ops = rangedOps(fs);
|
|
6775
6810
|
if (!ops) throw syncError("ENOSYS", path);
|
|
@@ -8384,6 +8419,8 @@ var VfsNode = class {
|
|
|
8384
8419
|
};
|
|
8385
8420
|
var VfsNodes = class {
|
|
8386
8421
|
byPath = /* @__PURE__ */ new Map();
|
|
8422
|
+
kept = /* @__PURE__ */ new Map();
|
|
8423
|
+
keepsOnDisk = false;
|
|
8387
8424
|
fs;
|
|
8388
8425
|
entries = /* @__PURE__ */ new Map();
|
|
8389
8426
|
onClosed;
|
|
@@ -8466,15 +8503,47 @@ var VfsNodes = class {
|
|
|
8466
8503
|
return;
|
|
8467
8504
|
}
|
|
8468
8505
|
if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
|
|
8506
|
+
this.kept.get(node)?.release();
|
|
8507
|
+
this.kept.delete(node);
|
|
8469
8508
|
this.onClosed?.(node.path);
|
|
8470
8509
|
}
|
|
8510
|
+
held() {
|
|
8511
|
+
return this.byPath.keys();
|
|
8512
|
+
}
|
|
8513
|
+
owns() {
|
|
8514
|
+
return false;
|
|
8515
|
+
}
|
|
8516
|
+
isKept() {
|
|
8517
|
+
return false;
|
|
8518
|
+
}
|
|
8519
|
+
keep(path, file) {
|
|
8520
|
+
const node = this.byPath.get(path);
|
|
8521
|
+
if (!node) {
|
|
8522
|
+
file.release();
|
|
8523
|
+
return;
|
|
8524
|
+
}
|
|
8525
|
+
this.byPath.delete(path);
|
|
8526
|
+
node.renamedTo(file.hidden);
|
|
8527
|
+
this.byPath.set(file.hidden, node);
|
|
8528
|
+
this.kept.set(node, file);
|
|
8529
|
+
}
|
|
8530
|
+
unkeep(path, file) {
|
|
8531
|
+
const node = this.byPath.get(file.hidden);
|
|
8532
|
+
if (!node) return;
|
|
8533
|
+
this.byPath.delete(file.hidden);
|
|
8534
|
+
node.renamedTo(path);
|
|
8535
|
+
this.byPath.set(path, node);
|
|
8536
|
+
this.kept.delete(node);
|
|
8537
|
+
}
|
|
8471
8538
|
async flush(path) {
|
|
8472
8539
|
for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
|
|
8473
8540
|
}
|
|
8474
8541
|
async unlinking(path) {
|
|
8475
8542
|
for (const [held, node] of [...this.byPath]) {
|
|
8476
8543
|
if (!within5(held, path)) continue;
|
|
8477
|
-
await node.serial(async () =>
|
|
8544
|
+
await node.serial(async () => {
|
|
8545
|
+
await Promise.all([this.keepsOnDisk || node.keep(), node.limit()]);
|
|
8546
|
+
});
|
|
8478
8547
|
}
|
|
8479
8548
|
}
|
|
8480
8549
|
revoke(prefix) {
|
|
@@ -8604,6 +8673,7 @@ function keepingOpen(fs, nodes) {
|
|
|
8604
8673
|
});
|
|
8605
8674
|
},
|
|
8606
8675
|
writeFile: (path, content) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content)),
|
|
8676
|
+
createExclusive: async (path) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path))),
|
|
8607
8677
|
symlink: (target, path) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path)),
|
|
8608
8678
|
mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
|
|
8609
8679
|
};
|
|
@@ -11262,15 +11332,20 @@ var Launcher = class {
|
|
|
11262
11332
|
enableCdp(this.net, this.cdp);
|
|
11263
11333
|
const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
11264
11334
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
11335
|
+
this.nodes.keepsOnDisk = true;
|
|
11265
11336
|
this.openFiles.add(this.nodes);
|
|
11337
|
+
const holders = [this.held, [this.nodes]];
|
|
11266
11338
|
this.unlinked = new UnlinkedKeeper(
|
|
11267
11339
|
fs,
|
|
11268
|
-
|
|
11340
|
+
{ [Symbol.iterator]: () => holders.flatMap((set2) => [...set2]).values() },
|
|
11269
11341
|
(path) => this.mountRoot(path),
|
|
11270
|
-
|
|
11271
|
-
|
|
11342
|
+
{
|
|
11343
|
+
locks: webLocks(),
|
|
11344
|
+
busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
|
|
11345
|
+
owner: options.unlinkOwner
|
|
11346
|
+
}
|
|
11272
11347
|
);
|
|
11273
|
-
this.fs =
|
|
11348
|
+
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
11274
11349
|
void this.unlinked.sweep("/");
|
|
11275
11350
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
11276
11351
|
this.catalog = void 0;
|