@ai-ecoverse/slicc-kernel 1.124.0 → 1.124.1

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 CHANGED
@@ -735,7 +735,7 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
735
735
 
736
736
  ### Exec
737
737
 
738
- 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. A rename over a held file stays atomic for everyone else, as `rename(2)` is: moving the held file aside and the new one into its place are two steps, so a lookup of that name (`open`, `stat`, a read) that comes while they run waits for both, and one that started before them and missed the name in between looks again (also when it spelled the name through a symlinked parent, which the wait doesn't match by path: a miss is retried whenever any replacement finished meanwhile, up to three tries). Every open finds the old file or the new one, never no file (#903). The OPFS root does the same for its own two steps: a rename onto an existing name replaces the file with `move()` (or, in a browser without it, a copy into the name and a removal of the source), and a lookup that loses that race to a stale handle looks again. A `tmpfs` mount replaces the name in one step; on `fsa` and `hostfs` mounts it is the real file system's `rename`. 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.
738
+ 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. A rename over a held file stays atomic for everyone else, as `rename(2)` is: moving the held file aside and the new one into its place are two steps, so a lookup of that name (`open`, `stat`, a read) that comes while they run waits for both, and one that started before them and missed the name in between looks again (also when it spelled the name through a symlinked parent, which the wait doesn't match by path: a miss is retried whenever any replacement finished meanwhile, up to three tries). Every open finds the old file or the new one, never no file (#903). A read through a held descriptor and the rename take the file's turn in order: lookups start to wait for the rename only once it has its turn on every held file it moves aside, so a read that came first finishes, and a rename over a file other processes keep reading never stalls them or itself (#980). A process that opens the file while the rename waits for its turn is a holder too: the rename looks for holders again once it has its turn, so that process keeps the old file as well, and a holder that closed meanwhile leaves nothing to move aside. The OPFS root does the same for its own two steps: a rename onto an existing name replaces the file with `move()` (or, in a browser without it, a copy into the name and a removal of the source), and a lookup that loses that race to a stale handle looks again. A `tmpfs` mount replaces the name in one step; on `fsa` and `hostfs` mounts it is the real file system's `rename`. 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.
739
739
 
740
740
  - **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).
741
741
  - **WASIX:** `proc_exec` does this by itself.
@@ -6565,7 +6565,7 @@ function satisfies(version, range2) {
6565
6565
  }
6566
6566
 
6567
6567
  // src/version.ts
6568
- var KERNEL_VERSION = true ? "1.124.0" : "0.0.0-dev";
6568
+ var KERNEL_VERSION = true ? "1.124.1" : "0.0.0-dev";
6569
6569
  var OS_RELEASE = "6.0.0-slicc";
6570
6570
  function procVersion() {
6571
6571
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -7468,18 +7468,19 @@ var UnlinkedKeeper = class {
7468
7468
  const keeper = this;
7469
7469
  const { replacing } = this;
7470
7470
  const replace = async (target, op) => {
7471
- const before = replacing.get(target);
7472
7471
  const { promise, resolve: resolve2 } = Promise.withResolvers();
7473
- const mine = {
7474
- done: Promise.all([before?.done, promise]).then(() => void 0)
7472
+ let mine;
7473
+ const gated = () => {
7474
+ const before = replacing.get(target);
7475
+ mine = { done: Promise.all([before?.done, promise]).then(() => void 0) };
7476
+ replacing.set(target, mine);
7475
7477
  };
7476
- replacing.set(target, mine);
7477
7478
  try {
7478
- await op();
7479
+ await op(gated);
7479
7480
  } finally {
7480
7481
  keeper.replaced += 1;
7481
7482
  resolve2();
7482
- if (replacing.get(target) === mine) replacing.delete(target);
7483
+ if (mine && replacing.get(target) === mine) replacing.delete(target);
7483
7484
  }
7484
7485
  };
7485
7486
  const unreplaced = async (path, op) => {
@@ -7524,8 +7525,8 @@ var UnlinkedKeeper = class {
7524
7525
  async rename(from, to) {
7525
7526
  const target = fs.resolvePath("/", to);
7526
7527
  if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
7527
- await replace(target, async () => {
7528
- const pinned = await keeper.pin(target);
7528
+ await replace(target, async (gated) => {
7529
+ const pinned = await keeper.pin(target, gated);
7529
7530
  await keeper.settle(pinned, () => fs.rename(from, to));
7530
7531
  });
7531
7532
  },
@@ -7542,7 +7543,7 @@ var UnlinkedKeeper = class {
7542
7543
  }
7543
7544
  pinned.commit();
7544
7545
  }
7545
- async pin(target) {
7546
+ heldWithin(target) {
7546
7547
  const held = /* @__PURE__ */ new Map();
7547
7548
  for (const holder of this.holders) {
7548
7549
  if (holder.owns(target)) continue;
@@ -7551,11 +7552,19 @@ var UnlinkedKeeper = class {
7551
7552
  held.set(path, [...held.get(path) ?? [], holder]);
7552
7553
  }
7553
7554
  }
7554
- const paths = [...held.keys()].sort();
7555
+ return held;
7556
+ }
7557
+ async pin(target, gated = () => void 0) {
7558
+ const paths = [...this.heldWithin(target).keys()].sort();
7555
7559
  const releases = [];
7556
7560
  for (const path of paths) releases.push(await this.gate.acquire(path));
7561
+ gated();
7562
+ const now = this.heldWithin(target);
7563
+ const held = new Map(
7564
+ paths.flatMap((path) => now.has(path) ? [[path, now.get(path)]] : [])
7565
+ );
7557
7566
  const hidden = /* @__PURE__ */ new Map();
7558
- for (const path of paths) {
7567
+ for (const path of held.keys()) {
7559
7568
  const at = await this.hide(path);
7560
7569
  if (at !== void 0) hidden.set(path, at);
7561
7570
  }