@ai-ecoverse/slicc-kernel 1.166.4 → 1.167.0

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
@@ -424,7 +424,7 @@ The module's default export (or its `main` export) is called with a context and
424
424
  - `sync.promiseState(promise)` (#1531, slicc-jsh#974) tells a native promise's state synchronously, as Node's `util.inspect` does: `{ state: 'pending' | 'fulfilled' | 'rejected', value }`. `value` is the very fulfillment value or rejection reason (`undefined` while pending), and probing doesn't handle a rejection. On the Node entry it reads the promise through an inspector session the process worker opens on first use, one per process and per thread; the probe runs no program code. Anything that isn't a native promise (a thenable, a proxy) is a `TypeError`. It answers `undefined` in a browser, where a worker has no inspector, on a Node built without one, once a program has changed `Object.prototype` or put a `toJSON` on a built-in prototype, and when the global object can't take a new property (`Object.preventExtensions(globalThis)`, or a frozen global).
425
425
  - `sync.setStackFormatter(fn | null)` (#1612, slicc-jsh#1091) is Node only, so check it with `typeof ctx.sync.setStackFormatter === 'function'`. It returns the previous formatter, and `null` removes it.
426
426
  - **When `fn` runs:** it formats an error's `.stack` the way V8's prepare-stack-trace callback does, as `fn(error, callSites)` returning the string. It runs only when no own `Error.prepareStackTrace` function is set, as in Node, so `Error.prepareStackTrace` stays an ordinary property: after `delete` it's `undefined`, `Object.getPrototypeOf(Error)` stays `Function.prototype`, and an assignment to a frozen subclass throws in strict mode.
427
- - **What `fn` gets:** a fresh array of V8 CallSites holding only the program's own frames (its modules, `data:` imports, `sync.runScript` scripts, code they `eval`, `new Function` or `eval` code named by `//# sourceURL`, #1632, and code such a body `eval`s, #1648), `node:` frames and native frames. Kernel frames are dropped by script identity, so a script named like a kernel file doesn't let them through, and so are host files. A program's strict frames give `getFunction()` and `getThis()` as `undefined`.
427
+ - **What `fn` gets:** a fresh array of V8 CallSites holding only the program's own frames (its modules, `data:` imports, `sync.runScript` scripts, code they `eval`, `new Function` or `eval` code named by `//# sourceURL`, #1632, and code such a body `eval`s, #1648; the kernel remembers the 4096 sourceURL names it saw last, #1658), `node:` frames and native frames. Kernel frames are dropped by script identity, so a script named like a kernel file doesn't let them through, and so are host files. A program's strict frames give `getFunction()` and `getThis()` as `undefined`.
428
428
  - **Fallback:** if `fn` throws or returns a non-string, or an error is formatted while `fn` runs, the default stack is used.
429
429
  - **`{ own: true }`** (#1631): `setStackFormatter(fn, { own: true })` calls `fn(error, callSites, own)` for every stack. `own` is the program's own `Error.prepareStackTrace` (the realm's `Error`, then the main one, read as Node 26 reads it), or `undefined` when none is set or it's Node's default. `fn`'s result is the stack whatever its type, and a throw propagates out of the `.stack` read, as with Node's own callback.
430
430
  - **An own `Error.prepareStackTrace` on the Node entry** (#1631): the process worker replaces Node's stack callback with a copy of Node 26's order. A program's own `Error.prepareStackTrace`, and a host preload's, still gets V8's CallSites as in Node, kernel frames included (named `slicc-kernel:/node-process-worker.js`), with its `Error` as receiver; its result and throws are the stack. To hand it the filtered CallSites instead (no kernel frame, no host path), use `setStackFormatter(fn, { own: true })` and call `own` from `fn`. 1.165.0 filtered by default, which broke runtimes that read their own frames through an own `prepareStackTrace`; the release after it went back to V8's CallSites. Node's own internal overrides still come first with raw frames, and so do the kernel's own diagnostics: a crash report and a WASI trap backtrace still show kernel and wasm frames.
@@ -1312,6 +1312,7 @@ var RemoteWatch = class {
1312
1312
  };
1313
1313
  const left = /* @__PURE__ */ new Set([to]);
1314
1314
  const timer = setTimeout(done5, cap);
1315
+ timer.unref?.();
1315
1316
  this.waiting.set(seq2, { left, done: done5 });
1316
1317
  });
1317
1318
  }
@@ -1353,6 +1354,9 @@ var RemoteWatch = class {
1353
1354
  this.keeps?.left(tab);
1354
1355
  this.ackers.delete(tab);
1355
1356
  this.holders.delete(tab);
1357
+ for (const wait2 of [...this.waiting.values()]) {
1358
+ if (wait2.left.delete(tab) && wait2.left.size === 0) wait2.done();
1359
+ }
1356
1360
  }
1357
1361
  }
1358
1362
  this.rebuild();
@@ -1598,9 +1602,9 @@ async function unlocked(name2, op) {
1598
1602
  return await op();
1599
1603
  } catch (err) {
1600
1604
  if (!LOCKED.has(err?.name)) throw err;
1601
- const delay = SYNC_BACKOFF_MS[attempt];
1602
- if (delay === void 0) throw fsError("EBUSY", name2);
1603
- await new Promise((resolve2) => setTimeout(resolve2, delay));
1605
+ const delay2 = SYNC_BACKOFF_MS[attempt];
1606
+ if (delay2 === void 0) throw fsError("EBUSY", name2);
1607
+ await new Promise((resolve2) => setTimeout(resolve2, delay2));
1604
1608
  }
1605
1609
  }
1606
1610
  }
@@ -1611,10 +1615,10 @@ async function openSync(handle) {
1611
1615
  try {
1612
1616
  return await sync.call(handle, { mode: "readwrite-unsafe" });
1613
1617
  } catch (err) {
1614
- const delay = SYNC_BACKOFF_MS[attempt];
1618
+ const delay2 = SYNC_BACKOFF_MS[attempt];
1615
1619
  if (!LOCKED.has(err?.name)) throw err;
1616
- if (delay === void 0) return void 0;
1617
- await new Promise((resolve2) => setTimeout(resolve2, delay));
1620
+ if (delay2 === void 0) return void 0;
1621
+ await new Promise((resolve2) => setTimeout(resolve2, delay2));
1618
1622
  }
1619
1623
  }
1620
1624
  }
@@ -3623,9 +3627,9 @@ var OpfsFs = class {
3623
3627
  if (!failed3) return true;
3624
3628
  const error = failed3.err?.name;
3625
3629
  if (handle.kind !== "file" || !LOCKED.has(error)) return false;
3626
- const delay = SYNC_BACKOFF_MS[attempt];
3627
- if (delay === void 0) throw fsError("EBUSY", name2);
3628
- await new Promise((resolve2) => setTimeout(resolve2, delay));
3630
+ const delay2 = SYNC_BACKOFF_MS[attempt];
3631
+ if (delay2 === void 0) throw fsError("EBUSY", name2);
3632
+ await new Promise((resolve2) => setTimeout(resolve2, delay2));
3629
3633
  }
3630
3634
  }
3631
3635
  async copy(handle, dir, name2, copies, rest2 = "") {
@@ -8464,7 +8468,7 @@ function satisfies(version, range2) {
8464
8468
  }
8465
8469
 
8466
8470
  // src/version.ts
8467
- var KERNEL_VERSION = true ? "1.166.4" : "0.0.0-dev";
8471
+ var KERNEL_VERSION = true ? "1.167.0" : "0.0.0-dev";
8468
8472
  var OS_RELEASE = "6.0.0-slicc";
8469
8473
  function procVersion() {
8470
8474
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9415,8 +9419,41 @@ var within4 = (path, target) => path === target || target === "/" || path.starts
9415
9419
  var TOUCHED_MAX = 4096;
9416
9420
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
9417
9421
  var PEER_RM_MS = 1e3;
9422
+ var SHARED_ACK_MS = 24 * 60 * 60 * 1e3;
9423
+ var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
9418
9424
  var PEER_RENAME_MS = 1e4;
9419
9425
  var PEER_GATE_MS = PEER_RENAME_MS + 5e3;
9426
+ var SHARED_PREFIX = "shared.";
9427
+ var keptLock = (hidden) => `slicc-kept:${hidden}`;
9428
+ function sharedKept(hidden) {
9429
+ const at = `/${UNLINKED_DIR}/${SHARED_PREFIX}`;
9430
+ return hidden.startsWith(at) && /^[0-9a-f]+$/.test(hidden.slice(at.length));
9431
+ }
9432
+ function holdShared(locks, name2) {
9433
+ if (!locks) return Promise.resolve(async () => void 0);
9434
+ return new Promise((resolve2) => {
9435
+ const settled = locks.request(name2, { mode: "shared" }, () => {
9436
+ const { promise, resolve: done5 } = Promise.withResolvers();
9437
+ resolve2(async () => {
9438
+ done5();
9439
+ await settled.catch(() => void 0);
9440
+ });
9441
+ return promise;
9442
+ });
9443
+ });
9444
+ }
9445
+ async function whenLast(locks, name2, op) {
9446
+ if (!locks) {
9447
+ await op();
9448
+ return true;
9449
+ }
9450
+ let free = false;
9451
+ await locks.request(name2, { mode: "exclusive", ifAvailable: true }, async (lock) => {
9452
+ free = lock !== null;
9453
+ if (free) await op();
9454
+ });
9455
+ return free;
9456
+ }
9420
9457
  var lockName = (instance) => `slicc-unlinked:${instance}`;
9421
9458
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
9422
9459
  var PathGate = class {
@@ -9732,52 +9769,124 @@ var UnlinkedKeeper = class {
9732
9769
  void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
9733
9770
  throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
9734
9771
  }
9735
- const { whole, sends } = await this.setAside(target, asked);
9736
- return { whole, undoing: (op) => this.undoing(op, peers, sends, heard) };
9772
+ const { whole, sends, shared } = await this.setAside(target, asked);
9773
+ return { whole, undoing: (op) => this.undoing(op, peers, sends, heard, shared) };
9737
9774
  }
9738
9775
  async setAside(target, asked) {
9739
9776
  const mine = this.heldWithin(target);
9740
- const taken = /* @__PURE__ */ new Set();
9741
- let whole = false;
9777
+ const tabsOf = /* @__PURE__ */ new Map();
9778
+ for (const { holders } of asked)
9779
+ for (const { path } of holders) tabsOf.set(path, (tabsOf.get(path) ?? 0) + 1);
9780
+ const done5 = /* @__PURE__ */ new Map();
9781
+ const shared = [];
9742
9782
  const sends = [];
9743
9783
  for (const { tab, holders, paths } of asked) {
9744
9784
  const items = [];
9745
9785
  for (const holder of holders) {
9746
- const free = !mine.has(holder.path) && !taken.has(holder.path);
9747
- const hidden = free ? await this.asideFor(holder.path, holder.instance) : void 0;
9748
- if (hidden !== void 0) {
9749
- taken.add(holder.path);
9750
- if (holder.path === target) whole = true;
9786
+ if (!done5.has(holder.path)) {
9787
+ const many = mine.has(holder.path) || (tabsOf.get(holder.path) ?? 0) > 1;
9788
+ done5.set(holder.path, await this.asideOne(holder, many, mine, shared));
9751
9789
  }
9752
- items.push({ path: holder.path, hidden, fid: holder.fid });
9790
+ items.push({ path: holder.path, hidden: done5.get(holder.path), fid: holder.fid });
9753
9791
  }
9754
9792
  sends.push({ tab, items, paths });
9755
9793
  }
9756
- return { whole, sends };
9794
+ return { whole: done5.get(target) !== void 0, sends, shared };
9795
+ }
9796
+ async asideOne(holder, many, mine, shared) {
9797
+ if (!many) return this.asideFor(holder.path, holder.instance);
9798
+ const one = await this.sharedAside(holder.path, mine);
9799
+ if (one) shared.push(one);
9800
+ return one?.hidden;
9801
+ }
9802
+ async sharedAside(path, mine) {
9803
+ const hidden = `${dirOf("/")}/${SHARED_PREFIX}${crypto.randomUUID().replaceAll("-", "")}`;
9804
+ const release = await holdShared(this.sharedLocks(), keptLock(hidden));
9805
+ const moved2 = await this.serial(async () => {
9806
+ try {
9807
+ const owner = this.dirOwner;
9808
+ await this.fs.mkdir(dirOf("/"), { recursive: true, ...owner ? { owner } : {} });
9809
+ await this.fs.rename(path, hidden);
9810
+ return true;
9811
+ } catch {
9812
+ return false;
9813
+ }
9814
+ });
9815
+ if (!moved2) {
9816
+ await release();
9817
+ return void 0;
9818
+ }
9819
+ const local2 = mine.get(path) ?? [];
9820
+ const file = local2.length > 0 ? await this.sharedFile(hidden, local2.length) : void 0;
9821
+ if (file) for (const holder of local2) holder.keep(path, file);
9822
+ return { path, hidden, release, local: local2, file };
9823
+ }
9824
+ async sharedFile(hidden, refs) {
9825
+ const locks = this.sharedLocks();
9826
+ const mine = await holdShared(locks, keptLock(hidden));
9827
+ let left = refs;
9828
+ return {
9829
+ hidden,
9830
+ release: () => {
9831
+ if (--left > 0) return;
9832
+ void mine().then(() => this.dropIfLast(hidden));
9833
+ }
9834
+ };
9757
9835
  }
9758
- async undoing(op, peers, sends, heard) {
9836
+ dropIfLast(hidden) {
9837
+ return this.serial(
9838
+ () => whenLast(this.sharedLocks(), keptLock(hidden), () => this.drop(hidden))
9839
+ );
9840
+ }
9841
+ sharedLocks() {
9842
+ return this.locks;
9843
+ }
9844
+ async undoing(op, peers, sends, heard, shared = []) {
9759
9845
  let failed3;
9760
9846
  let out;
9761
9847
  try {
9762
9848
  out = await op();
9763
9849
  } catch (err) {
9764
9850
  failed3 = err ?? new Error("failed");
9765
- for (const send2 of sends) {
9766
- for (const item of send2.items) {
9767
- const { hidden, path } = item;
9768
- if (hidden !== void 0 && await this.moveBack(hidden, path)) item.hidden = void 0;
9769
- }
9770
- }
9851
+ await this.rollBackAsides(sends, shared);
9771
9852
  }
9853
+ const hiddenShared = new Set(shared.map((one) => one.hidden));
9854
+ const answers2 = sends.map(({ tab, items, paths }) => {
9855
+ const keeps = items.some((item) => item.hidden && hiddenShared.has(item.hidden));
9856
+ return {
9857
+ keeps,
9858
+ told: peers.ask(tab, { kept: { items, paths } }, keeps ? SHARED_ACK_MS : PEER_RM_MS)
9859
+ };
9860
+ });
9772
9861
  await Promise.all(
9773
- sends.map(({ tab, items, paths }, i) => {
9774
- const told = peers.ask(tab, { kept: { items, paths } }, PEER_RM_MS);
9775
- return heard[i] ? told : void 0;
9776
- })
9862
+ answers2.map(({ told }, i) => heard[i] ? Promise.race([told, delay(PEER_RM_MS)]) : void 0)
9777
9863
  );
9864
+ for (const one of shared) {
9865
+ const waits = answers2.filter(
9866
+ ({ keeps }, i) => keeps && sends[i]?.items.some((item) => item.hidden === one.hidden)
9867
+ );
9868
+ const done5 = () => one.release().then(() => this.dropIfLast(one.hidden));
9869
+ void Promise.all(waits.map(({ told }) => told)).then(done5, done5);
9870
+ }
9778
9871
  if (failed3 !== void 0) throw failed3;
9779
9872
  return out;
9780
9873
  }
9874
+ async rollBackAsides(sends, shared) {
9875
+ const back = /* @__PURE__ */ new Map();
9876
+ for (const item of sends.flatMap((send2) => send2.items)) {
9877
+ const { hidden, path } = item;
9878
+ if (hidden === void 0) continue;
9879
+ if (!back.has(hidden)) back.set(hidden, await this.moveBack(hidden, path));
9880
+ if (back.get(hidden)) item.hidden = void 0;
9881
+ }
9882
+ for (const { hidden, path, local: local2, file } of shared) {
9883
+ if (!back.get(hidden) || !file) continue;
9884
+ for (const holder of local2) {
9885
+ holder.unkeep(path, file);
9886
+ file.release();
9887
+ }
9888
+ }
9889
+ }
9781
9890
  moveBack(hidden, path) {
9782
9891
  return this.serial(async () => {
9783
9892
  if (await this.present(path)) return false;
@@ -9886,6 +9995,15 @@ var UnlinkedKeeper = class {
9886
9995
  for (const name2 of names2) {
9887
9996
  const id2 = name2.slice(0, name2.lastIndexOf("."));
9888
9997
  const at = id2.indexOf("~");
9998
+ if (name2.startsWith(SHARED_PREFIX)) {
9999
+ const at2 = `${dir}/${name2}`;
10000
+ await whenLast(
10001
+ this.sharedLocks(),
10002
+ keptLock(at2),
10003
+ () => this.fs.rm(at2, { recursive: true }).catch(() => void 0)
10004
+ );
10005
+ continue;
10006
+ }
9889
10007
  const dead = id2 !== this.instance && (at < 0 ? held !== void 0 && !held.has(lockName(id2)) : owner?.alive(id2.slice(0, at)) === false);
9890
10008
  if (dead) await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
9891
10009
  }
@@ -24598,13 +24716,13 @@ var VfsNode = class {
24598
24716
  this.recount();
24599
24717
  if (!this.orphaned) this.onDirty?.();
24600
24718
  if (this.writeBack !== void 0 || this.orphaned) return;
24601
- const delay = Math.max(WRITEBACK_MS, this.writeBackCost * WRITEBACK_COST_FACTOR);
24719
+ const delay2 = Math.max(WRITEBACK_MS, this.writeBackCost * WRITEBACK_COST_FACTOR);
24602
24720
  this.writeBack = setTimeout(() => {
24603
24721
  this.writeBack = void 0;
24604
24722
  this.serial(() => this.flush()).catch(() => {
24605
24723
  this.failed = true;
24606
24724
  });
24607
- }, delay);
24725
+ }, delay2);
24608
24726
  }
24609
24727
  ensure(need2) {
24610
24728
  const cur = this.data ?? new Uint8Array(0);
@@ -33572,7 +33690,10 @@ var Launcher = class {
33572
33690
  await Promise.all(mine);
33573
33691
  },
33574
33692
  kept: async (tab, items, paths) => {
33575
- for (const { path, hidden, fid } of items) this.adoptKept(path, hidden, fid, ours);
33693
+ for (const { path, hidden, fid } of items) {
33694
+ if (hidden !== void 0 && sharedKept(hidden)) await this.adoptShared(path, hidden, fid);
33695
+ else this.adoptKept(path, hidden, fid, ours);
33696
+ }
33576
33697
  await open2(tab, paths);
33577
33698
  },
33578
33699
  left: (tab) => {
@@ -33581,6 +33702,33 @@ var Launcher = class {
33581
33702
  }
33582
33703
  };
33583
33704
  }
33705
+ async adoptShared(path, hidden, fid) {
33706
+ const bound3 = [...this.held].map(
33707
+ (holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
33708
+ );
33709
+ const keeping = bound3.filter(([, ids]) => ids.length > 0);
33710
+ const nodes = [...this.openFiles].filter(
33711
+ (open2) => [path, hidden].some((at) => open2.holdsAt(at))
33712
+ );
33713
+ const refs = keeping.length + nodes.length;
33714
+ if (refs === 0) {
33715
+ this.held.doubt([path]);
33716
+ return;
33717
+ }
33718
+ const file = await this.unlinked.sharedFile(hidden, refs);
33719
+ if (!await this.rootFs.lstat(hidden).then(
33720
+ () => true,
33721
+ () => false
33722
+ )) {
33723
+ for (let i = 0; i < refs; i++) file.release();
33724
+ this.held.doubt([path]);
33725
+ return;
33726
+ }
33727
+ const kept = nodes.filter((open2) => open2.keepBound([hidden, path], fid, file)).length;
33728
+ for (let i = kept; i < nodes.length; i++) file.release();
33729
+ for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
33730
+ void this.rootFs.announceHolds?.([], [path, hidden], false);
33731
+ }
33584
33732
  adoptKept(path, hidden, fid, ours) {
33585
33733
  if (hidden === void 0 || !ours(hidden)) {
33586
33734
  this.held.doubt([path]);
@@ -33723,7 +33871,6 @@ var Launcher = class {
33723
33871
  moved(from, to, source) {
33724
33872
  this.renameLog.add(from, to);
33725
33873
  for (const holds2 of this.held) holds2.renamed(from, to);
33726
- if (this.mounts.locate(to) === void 0) this.rootFs.rehold?.(from, to);
33727
33874
  if (source) return;
33728
33875
  for (const nodes of this.openFiles) nodes.renamed(from, to);
33729
33876
  this.watchers.moved(from, to);