@ai-ecoverse/slicc-kernel 1.39.1 → 1.39.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 +102 -26
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +82 -14
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +291 -201
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +82 -14
- package/dist/process-worker.js.map +3 -3
- 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
|
});
|
|
@@ -5653,6 +5664,7 @@ var ChildTable = class {
|
|
|
5653
5664
|
if ("fd" in slot) return this.parentFds.get(slot.fd).retain();
|
|
5654
5665
|
if ("input" in slot) return bytesSource(slot.input);
|
|
5655
5666
|
if ("device" in slot) return deviceFile(slot.device, slot.access);
|
|
5667
|
+
if ("dir" in slot) return heldFile({ dir: slot.dir });
|
|
5656
5668
|
if ("capture" in slot) {
|
|
5657
5669
|
const chunks = [];
|
|
5658
5670
|
captured.set(n, chunks);
|
|
@@ -5884,6 +5896,18 @@ var HeldPaths = class extends Map {
|
|
|
5884
5896
|
holding(path) {
|
|
5885
5897
|
return this.opens.has(this.keyOf(path));
|
|
5886
5898
|
}
|
|
5899
|
+
held() {
|
|
5900
|
+
return this.opens.keys();
|
|
5901
|
+
}
|
|
5902
|
+
isKept(path) {
|
|
5903
|
+
return this.unlinked.has(path);
|
|
5904
|
+
}
|
|
5905
|
+
keep(path, file) {
|
|
5906
|
+
this.unlinked.set(path, file);
|
|
5907
|
+
}
|
|
5908
|
+
unkeep(path) {
|
|
5909
|
+
this.unlinked.delete(path);
|
|
5910
|
+
}
|
|
5887
5911
|
hold(path, on, open = false, real) {
|
|
5888
5912
|
if (open) {
|
|
5889
5913
|
const key = real ?? this.keyOf(path);
|
|
@@ -6751,6 +6775,9 @@ async function run(entry, path, req) {
|
|
|
6751
6775
|
case "symlink":
|
|
6752
6776
|
await fs.symlink(req.arg2 ?? "", path);
|
|
6753
6777
|
return done;
|
|
6778
|
+
case "create":
|
|
6779
|
+
await createExclusive(fs, path);
|
|
6780
|
+
return done;
|
|
6754
6781
|
case "readlink":
|
|
6755
6782
|
return json2(await fs.readlink(path));
|
|
6756
6783
|
case "chmod":
|
|
@@ -6770,6 +6797,15 @@ async function run(entry, path, req) {
|
|
|
6770
6797
|
return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
|
|
6771
6798
|
}
|
|
6772
6799
|
}
|
|
6800
|
+
async function createExclusive(fs, path) {
|
|
6801
|
+
if (fs.createExclusive) return fs.createExclusive(path);
|
|
6802
|
+
if (await fs.lstat(path).then(
|
|
6803
|
+
() => true,
|
|
6804
|
+
() => false
|
|
6805
|
+
))
|
|
6806
|
+
throw syncError("EEXIST", path);
|
|
6807
|
+
await fs.writeFile(path, new Uint8Array(0));
|
|
6808
|
+
}
|
|
6773
6809
|
async function ranged(fs, path, req) {
|
|
6774
6810
|
const ops = rangedOps(fs);
|
|
6775
6811
|
if (!ops) throw syncError("ENOSYS", path);
|
|
@@ -8384,6 +8420,8 @@ var VfsNode = class {
|
|
|
8384
8420
|
};
|
|
8385
8421
|
var VfsNodes = class {
|
|
8386
8422
|
byPath = /* @__PURE__ */ new Map();
|
|
8423
|
+
kept = /* @__PURE__ */ new Map();
|
|
8424
|
+
keepsOnDisk = false;
|
|
8387
8425
|
fs;
|
|
8388
8426
|
entries = /* @__PURE__ */ new Map();
|
|
8389
8427
|
onClosed;
|
|
@@ -8466,15 +8504,47 @@ var VfsNodes = class {
|
|
|
8466
8504
|
return;
|
|
8467
8505
|
}
|
|
8468
8506
|
if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
|
|
8507
|
+
this.kept.get(node)?.release();
|
|
8508
|
+
this.kept.delete(node);
|
|
8469
8509
|
this.onClosed?.(node.path);
|
|
8470
8510
|
}
|
|
8511
|
+
held() {
|
|
8512
|
+
return this.byPath.keys();
|
|
8513
|
+
}
|
|
8514
|
+
owns() {
|
|
8515
|
+
return false;
|
|
8516
|
+
}
|
|
8517
|
+
isKept() {
|
|
8518
|
+
return false;
|
|
8519
|
+
}
|
|
8520
|
+
keep(path, file) {
|
|
8521
|
+
const node = this.byPath.get(path);
|
|
8522
|
+
if (!node) {
|
|
8523
|
+
file.release();
|
|
8524
|
+
return;
|
|
8525
|
+
}
|
|
8526
|
+
this.byPath.delete(path);
|
|
8527
|
+
node.renamedTo(file.hidden);
|
|
8528
|
+
this.byPath.set(file.hidden, node);
|
|
8529
|
+
this.kept.set(node, file);
|
|
8530
|
+
}
|
|
8531
|
+
unkeep(path, file) {
|
|
8532
|
+
const node = this.byPath.get(file.hidden);
|
|
8533
|
+
if (!node) return;
|
|
8534
|
+
this.byPath.delete(file.hidden);
|
|
8535
|
+
node.renamedTo(path);
|
|
8536
|
+
this.byPath.set(path, node);
|
|
8537
|
+
this.kept.delete(node);
|
|
8538
|
+
}
|
|
8471
8539
|
async flush(path) {
|
|
8472
8540
|
for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
|
|
8473
8541
|
}
|
|
8474
8542
|
async unlinking(path) {
|
|
8475
8543
|
for (const [held, node] of [...this.byPath]) {
|
|
8476
8544
|
if (!within5(held, path)) continue;
|
|
8477
|
-
await node.serial(async () =>
|
|
8545
|
+
await node.serial(async () => {
|
|
8546
|
+
await Promise.all([this.keepsOnDisk || node.keep(), node.limit()]);
|
|
8547
|
+
});
|
|
8478
8548
|
}
|
|
8479
8549
|
}
|
|
8480
8550
|
revoke(prefix) {
|
|
@@ -8604,6 +8674,7 @@ function keepingOpen(fs, nodes) {
|
|
|
8604
8674
|
});
|
|
8605
8675
|
},
|
|
8606
8676
|
writeFile: (path, content) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content)),
|
|
8677
|
+
createExclusive: async (path) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path))),
|
|
8607
8678
|
symlink: (target, path) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path)),
|
|
8608
8679
|
mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
|
|
8609
8680
|
};
|
|
@@ -11262,15 +11333,20 @@ var Launcher = class {
|
|
|
11262
11333
|
enableCdp(this.net, this.cdp);
|
|
11263
11334
|
const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
11264
11335
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
11336
|
+
this.nodes.keepsOnDisk = true;
|
|
11265
11337
|
this.openFiles.add(this.nodes);
|
|
11338
|
+
const holders = [this.held, [this.nodes]];
|
|
11266
11339
|
this.unlinked = new UnlinkedKeeper(
|
|
11267
11340
|
fs,
|
|
11268
|
-
|
|
11341
|
+
{ [Symbol.iterator]: () => holders.flatMap((set2) => [...set2]).values() },
|
|
11269
11342
|
(path) => this.mountRoot(path),
|
|
11270
|
-
|
|
11271
|
-
|
|
11343
|
+
{
|
|
11344
|
+
locks: webLocks(),
|
|
11345
|
+
busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
|
|
11346
|
+
owner: options.unlinkOwner
|
|
11347
|
+
}
|
|
11272
11348
|
);
|
|
11273
|
-
this.fs =
|
|
11349
|
+
this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
|
|
11274
11350
|
void this.unlinked.sweep("/");
|
|
11275
11351
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
11276
11352
|
this.catalog = void 0;
|