@ai-ecoverse/slicc-kernel 1.141.3 → 1.142.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.
@@ -165,6 +165,9 @@ var IndexedDbMeta = class {
165
165
  };
166
166
  this.channel.unref?.();
167
167
  }
168
+ forgetTrees(trees) {
169
+ this.drop({ trees: [...trees] });
170
+ }
168
171
  drop({ paths = [], trees = [] }) {
169
172
  this.generation++;
170
173
  for (const path of paths) this.cache.delete(path);
@@ -353,6 +356,90 @@ async function openMeta(name2, factory) {
353
356
  return new IndexedDbMeta(await openMetaDb(name2, factory));
354
357
  }
355
358
 
359
+ // src/fs/changes.ts
360
+ var GEN = 0;
361
+ var RING = 1;
362
+ var RING_SIZE = 4096;
363
+ var SLOT = 2;
364
+ var SHARED_TTL_MS = 100;
365
+ var DIRTY = RING + RING_SIZE * SLOT;
366
+ var FOREIGN = DIRTY + 1;
367
+ var CHANGES_BYTES = (FOREIGN + 1) * 4;
368
+ function changeHash(path) {
369
+ let h = 2166136261;
370
+ for (let i = 0; i < path.length; i++) {
371
+ h ^= path.charCodeAt(i);
372
+ h = Math.imul(h, 16777619);
373
+ }
374
+ return h === 0 ? 1 : h;
375
+ }
376
+ function normal(path) {
377
+ const parts2 = [];
378
+ for (const part of path.split("/")) {
379
+ if (part === "" || part === ".") continue;
380
+ if (part === "..") parts2.pop();
381
+ else parts2.push(part);
382
+ }
383
+ return `/${parts2.join("/")}`;
384
+ }
385
+ var FileChanges = class {
386
+ sab;
387
+ view;
388
+ constructor(sab = new SharedArrayBuffer(CHANGES_BYTES)) {
389
+ this.sab = sab;
390
+ this.view = new Int32Array(sab);
391
+ }
392
+ dirtied(delta) {
393
+ Atomics.add(this.view, DIRTY, delta);
394
+ }
395
+ foreign(on) {
396
+ Atomics.store(this.view, FOREIGN, on ? 1 : 0);
397
+ }
398
+ flush() {
399
+ Atomics.add(this.view, GEN, RING_SIZE + 1);
400
+ }
401
+ publish(...paths) {
402
+ for (const path of paths) {
403
+ const g = Atomics.add(this.view, GEN, 1) + 1 | 0;
404
+ const at = RING + (g & RING_SIZE - 1) * SLOT;
405
+ Atomics.store(this.view, at, 0);
406
+ Atomics.store(this.view, at + 1, changeHash(normal(path)));
407
+ Atomics.store(this.view, at, g);
408
+ }
409
+ }
410
+ };
411
+ var ChangeView = class {
412
+ view;
413
+ constructor(sab) {
414
+ this.view = new Int32Array(sab);
415
+ }
416
+ now() {
417
+ return Atomics.load(this.view, GEN);
418
+ }
419
+ clean() {
420
+ return this.view.length > DIRTY && Atomics.load(this.view, DIRTY) === 0;
421
+ }
422
+ foreign() {
423
+ return this.view.length > FOREIGN && Atomics.load(this.view, FOREIGN) === 1;
424
+ }
425
+ changedSince(seen, path) {
426
+ const now = Atomics.load(this.view, GEN);
427
+ const distance = now - seen >>> 0;
428
+ if (distance === 0) return { now, changed: false };
429
+ if (distance > RING_SIZE) return { now, changed: true };
430
+ const hash = changeHash(normal(path));
431
+ for (let i = 1; i <= distance; i++) {
432
+ const g = seen + i | 0;
433
+ const at = RING + (g & RING_SIZE - 1) * SLOT;
434
+ if (Atomics.load(this.view, at) !== g) return { now, changed: true };
435
+ const hit = Atomics.load(this.view, at + 1) === hash;
436
+ if (Atomics.load(this.view, at) !== g) return { now, changed: true };
437
+ if (hit) return { now, changed: true };
438
+ }
439
+ return { now, changed: false };
440
+ }
441
+ };
442
+
356
443
  // src/fs/notice.ts
357
444
  function dirsChannel(metadata) {
358
445
  return `slicc-kernel-dirs:${metadata}`;
@@ -881,6 +968,8 @@ var ANNOUNCE_WAIT_MS = 500;
881
968
  var PRUNE_EVERY_MS = 3e4;
882
969
  var MOURN_TRIES = 5;
883
970
  var MOURN_RETRY_MS = 100;
971
+ var RENAME_ACK_MAX_MS = 1e4;
972
+ var ANNOUNCE_POLL_MS = 20;
884
973
  function lockManager() {
885
974
  return globalThis.navigator?.locks;
886
975
  }
@@ -904,6 +993,9 @@ var RemoteWatch = class {
904
993
  quiet;
905
994
  crowd;
906
995
  gone;
996
+ renaming;
997
+ renamed;
998
+ ackers = /* @__PURE__ */ new Set();
907
999
  told;
908
1000
  counted = 0;
909
1001
  recount() {
@@ -931,7 +1023,7 @@ var RemoteWatch = class {
931
1023
  this.settled = true;
932
1024
  this.recount();
933
1025
  });
934
- this.channel.postMessage({ hello: this.tab });
1026
+ this.channel.postMessage({ hello: this.tab, renames: true });
935
1027
  }
936
1028
  async join() {
937
1029
  if (!this.locks) return;
@@ -952,6 +1044,20 @@ var RemoteWatch = class {
952
1044
  await this.joined;
953
1045
  return this.peers.size > 0;
954
1046
  }
1047
+ async joinedAll(cap) {
1048
+ if (!this.locks) return true;
1049
+ const prefix = `${this.name}:`;
1050
+ const began = performance.now();
1051
+ for (; ; ) {
1052
+ const held = ((await this.locks.query()).held ?? []).map((lock) => lock.name ?? "");
1053
+ const silent = held.some(
1054
+ (name2) => name2.startsWith(prefix) && name2 !== this.lockOf(this.tab) && !this.peers.has(name2.slice(prefix.length))
1055
+ );
1056
+ if (!silent) return true;
1057
+ if (performance.now() - began >= cap) return false;
1058
+ await new Promise((resolve2) => setTimeout(resolve2, ANNOUNCE_POLL_MS));
1059
+ }
1060
+ }
955
1061
  crowded() {
956
1062
  return !this.settled || this.peers.size > 0;
957
1063
  }
@@ -959,7 +1065,11 @@ var RemoteWatch = class {
959
1065
  return `${this.name}:${tab}`;
960
1066
  }
961
1067
  receive(data) {
1068
+ const sender = data.hello ?? data.here;
1069
+ if (sender !== void 0 && data.renames) this.ackers.add(sender);
962
1070
  if (data.hello !== void 0) this.greeted(data.hello);
1071
+ if (data.renaming !== void 0 && data.tab !== void 0) this.heard(data);
1072
+ if (data.renamed !== void 0) this.renamed?.(data.renamed);
963
1073
  if (data.here !== void 0 && data.to === this.tab) {
964
1074
  this.arrived(data.here);
965
1075
  this.met?.();
@@ -991,6 +1101,7 @@ var RemoteWatch = class {
991
1101
  this.mourned.delete(peer);
992
1102
  this.others.delete(peer);
993
1103
  this.peers.delete(peer);
1104
+ this.ackers.delete(peer);
994
1105
  this.rebuild();
995
1106
  this.recount();
996
1107
  this.gone?.();
@@ -1002,12 +1113,18 @@ var RemoteWatch = class {
1002
1113
  greeted(peer) {
1003
1114
  this.arrived(peer);
1004
1115
  const answer2 = () => {
1005
- this.channel.postMessage({ here: this.tab, to: peer });
1116
+ this.channel.postMessage({ here: this.tab, to: peer, renames: true });
1006
1117
  if (this.mine.length > 0) void this.announce(this.mine);
1007
1118
  };
1008
1119
  if (this.quiet) void this.quiet().then(answer2, answer2);
1009
1120
  else answer2();
1010
1121
  }
1122
+ heard({ tab, renaming, seq: seq2 }) {
1123
+ const ack = () => this.channel.postMessage({ ack: seq2, from: this.tab, to: tab });
1124
+ const heard = this.renaming?.(renaming);
1125
+ if (heard !== void 0) void heard.then(ack, ack);
1126
+ else ack();
1127
+ }
1011
1128
  announced({ tab, paths, seq: seq2 }) {
1012
1129
  const from = tab;
1013
1130
  this.arrived(from);
@@ -1034,6 +1151,7 @@ var RemoteWatch = class {
1034
1151
  if (!held.has(this.lockOf(tab))) {
1035
1152
  if (this.peers.delete(tab)) left = true;
1036
1153
  this.others.delete(tab);
1154
+ this.ackers.delete(tab);
1037
1155
  }
1038
1156
  }
1039
1157
  this.rebuild();
@@ -1055,7 +1173,34 @@ var RemoteWatch = class {
1055
1173
  this.mine = [...paths];
1056
1174
  const seq2 = ++this.seq;
1057
1175
  this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
1058
- if (this.peers.size === 0) return Promise.resolve();
1176
+ return this.acks(seq2, this.peers);
1177
+ }
1178
+ renamingTree(rename, cap = RENAME_ACK_MAX_MS) {
1179
+ const seq2 = ++this.seq;
1180
+ this.channel.postMessage({ tab: this.tab, renaming: rename, seq: seq2 });
1181
+ const left = new Set(this.ackers);
1182
+ if (left.size === 0) return Promise.resolve([]);
1183
+ const began = performance.now();
1184
+ return new Promise((resolve2) => {
1185
+ const done5 = () => {
1186
+ clearInterval(timer);
1187
+ this.waiting.delete(seq2);
1188
+ resolve2([...left]);
1189
+ };
1190
+ const timer = setInterval(() => {
1191
+ void this.prune().then(() => {
1192
+ for (const peer of left) if (!this.ackers.has(peer)) left.delete(peer);
1193
+ if (left.size === 0 || performance.now() - began >= cap) done5();
1194
+ });
1195
+ }, ANNOUNCE_WAIT_MS);
1196
+ this.waiting.set(seq2, { left, done: done5 });
1197
+ });
1198
+ }
1199
+ renamedTree(lock) {
1200
+ this.channel.postMessage({ renamed: lock });
1201
+ }
1202
+ acks(seq2, from) {
1203
+ if (from.size === 0) return Promise.resolve();
1059
1204
  return new Promise((resolve2) => {
1060
1205
  const done5 = () => {
1061
1206
  clearTimeout(timer);
@@ -1066,7 +1211,7 @@ var RemoteWatch = class {
1066
1211
  done5();
1067
1212
  void this.prune();
1068
1213
  }, ANNOUNCE_WAIT_MS);
1069
- this.waiting.set(seq2, { left: new Set(this.peers), done: done5 });
1214
+ this.waiting.set(seq2, { left: new Set(from), done: done5 });
1070
1215
  });
1071
1216
  }
1072
1217
  emit(change) {
@@ -1129,6 +1274,12 @@ var RECONCILE_LISTINGS = 8;
1129
1274
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
1130
1275
  var RENAMES = "/.slicc-renames";
1131
1276
  var RENAME_LOCK = "slicc-rename:";
1277
+ var RENAME_ADMIT = "slicc-rename-admit:";
1278
+ var PAIRED = /* @__PURE__ */ new Set(["rename", "link", "copyFile"]);
1279
+ var LISTINGS = /* @__PURE__ */ new Set(["readdir", "readdirStat"]);
1280
+ var RETRY = /* @__PURE__ */ Symbol("retry");
1281
+ var ACK_SAMPLES = 4096;
1282
+ var INTENT_POLL_MS = 5;
1132
1283
  function movable(handle) {
1133
1284
  return typeof handle.move === "function";
1134
1285
  }
@@ -1173,6 +1324,7 @@ function sidecar(meta) {
1173
1324
  for (const { path, change } of changes) await ops.update(path, change);
1174
1325
  };
1175
1326
  if (meta.absent) ops.absent = meta.absent.bind(meta);
1327
+ if (meta.forgetTrees) ops.forgetTrees = meta.forgetTrees.bind(meta);
1176
1328
  const { removeFile } = meta;
1177
1329
  if (removeFile)
1178
1330
  ops.removeFile = (path) => removeFile.call(meta, path).catch((err) => {
@@ -1298,7 +1450,17 @@ var OpfsFs = class {
1298
1450
  this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`, { locks: this.locks ?? null });
1299
1451
  if (this.remote) {
1300
1452
  this.remote.quiet = () => this.quiet().then(() => this.settle?.());
1301
- this.remote.gone = () => void this.orphans();
1453
+ this.remote.gone = () => {
1454
+ void this.orphans();
1455
+ for (const move of [...this.peerMoves.values()]) void this.peerMoved(move);
1456
+ };
1457
+ this.remote.renaming = (rename) => this.heardRename(rename);
1458
+ this.remote.renamed = (lock) => {
1459
+ const move = this.peerMoves.get(lock);
1460
+ if (!move) return;
1461
+ move.done = true;
1462
+ void this.peerMoved(move);
1463
+ };
1302
1464
  }
1303
1465
  if (this.channel) {
1304
1466
  const remote = this.remote;
@@ -2342,16 +2504,27 @@ var OpfsFs = class {
2342
2504
  throw fsError("EPERM", `${from}: this file system has no hard links`);
2343
2505
  }
2344
2506
  async rename(from, to) {
2507
+ const call = this.renameCall;
2508
+ this.renameCall = void 0;
2345
2509
  if (from === to) return;
2510
+ for (; ; ) {
2511
+ try {
2512
+ return await this.renameOnce(from, to, call);
2513
+ } catch (err) {
2514
+ if (err !== RETRY) throw err;
2515
+ }
2516
+ }
2517
+ }
2518
+ async renameOnce(from, to, call) {
2346
2519
  const source = await this.locate(from, false);
2347
2520
  this.refuse(source.path, true);
2348
2521
  this.forget(source.path);
2349
2522
  const parent = await this.locate(parentOf3(to), true);
2350
2523
  const destination = resolvePath(parent.path, baseOf(to));
2351
2524
  this.refuse(destination, true);
2352
- await this.entering(destination, () => this.moveTo(source, parent, destination, to));
2525
+ await this.entering(destination, () => this.moveTo(source, parent, destination, to, call));
2353
2526
  }
2354
- async moveTo(source, parent, destination, to) {
2527
+ async moveTo(source, parent, destination, to, call) {
2355
2528
  const sourceStat = await this.describe(source);
2356
2529
  if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
2357
2530
  const target = await this.exactly(() => this.locate(destination, false)).catch((err) => {
@@ -2386,6 +2559,7 @@ var OpfsFs = class {
2386
2559
  const name2 = baseOf(destination);
2387
2560
  const swap = targetStat?.isDirectory === true;
2388
2561
  let lock;
2562
+ const hooks = {};
2389
2563
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
2390
2564
  const copying = sourceStat.isDirectory && !movable(source.handle);
2391
2565
  const staged = resolvePath(parent.path, staging);
@@ -2394,8 +2568,15 @@ var OpfsFs = class {
2394
2568
  const copies = /* @__PURE__ */ new Map();
2395
2569
  const root = { ino: inodeOf(source.path), ...moving };
2396
2570
  const owned4 = lock ? { lock } : {};
2571
+ hooks.notify?.();
2397
2572
  const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
2398
2573
  const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
2574
+ await hooks.started?.().catch(async (err) => {
2575
+ for (const intent of [first, second]) {
2576
+ if (intent) await this.meta.update(intent, () => void 0);
2577
+ }
2578
+ throw err;
2579
+ });
2399
2580
  const moved2 = await this.replacingTarget(
2400
2581
  targetStat !== null,
2401
2582
  () => this.place(source.handle, dir, staging, copies)
@@ -2430,13 +2611,262 @@ var OpfsFs = class {
2430
2611
  const locks = this.locks;
2431
2612
  if (locks) {
2432
2613
  lock = `${RENAME_LOCK}${crypto.randomUUID()}`;
2433
- await locks.request(lock, place);
2614
+ this.ownLocks.add(lock);
2615
+ call?.release();
2616
+ await this.admitted(locks, lock, source.path, destination, place, hooks);
2434
2617
  } else await place();
2435
2618
  } finally {
2619
+ if (lock) this.ownLocks.delete(lock);
2436
2620
  this.renamingDirs.delete(entry);
2437
2621
  resolve2();
2438
2622
  }
2439
2623
  }
2624
+ async admitted(locks, lock, from, to, place, hooks) {
2625
+ const remote = this.remote;
2626
+ if (remote && !this.shared() && !await remote.joinedAll(RENAME_ACK_MAX_MS)) {
2627
+ this.ackBusy++;
2628
+ console.warn(
2629
+ `slicc-kernel: renaming ${from} to ${to} gave up (EBUSY): a tab sharing the root did not finish joining in ${RENAME_ACK_MAX_MS} ms (#1140)`
2630
+ );
2631
+ throw fsError("EBUSY", from);
2632
+ }
2633
+ if (!remote || !this.shared()) {
2634
+ await locks.request(lock, place);
2635
+ return;
2636
+ }
2637
+ let clash;
2638
+ let run2;
2639
+ await locks.request(`${RENAME_ADMIT}${this.rootName ?? ""}`, async () => {
2640
+ if (await this.renamingElsewhere()) await this.learnRenames();
2641
+ clash = this.clashing(from, to);
2642
+ if (clash) return;
2643
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
2644
+ let told;
2645
+ hooks.notify = () => {
2646
+ told = remote.renamingTree({ from, to, lock });
2647
+ };
2648
+ hooks.started = async () => {
2649
+ resolve2();
2650
+ this.renameAcks++;
2651
+ const at = performance.now();
2652
+ const stuck = await told;
2653
+ this.ackMs.push(performance.now() - at);
2654
+ if (this.ackMs.length > ACK_SAMPLES) this.ackMs.shift();
2655
+ if (stuck.length === 0) return;
2656
+ this.ackBusy++;
2657
+ console.warn(
2658
+ `slicc-kernel: renaming ${from} to ${to} gave up (EBUSY): peer tab ${stuck.join(", ")} is alive but did not answer in ${RENAME_ACK_MAX_MS} ms (#1140)`
2659
+ );
2660
+ throw fsError("EBUSY", from);
2661
+ };
2662
+ run2 = locks.request(lock, async () => {
2663
+ await place();
2664
+ remote.renamedTree(lock);
2665
+ });
2666
+ await Promise.race([promise, run2.then(nothing, nothing)]);
2667
+ });
2668
+ if (clash) {
2669
+ this.peerWaited++;
2670
+ await this.peerMoved(clash);
2671
+ throw RETRY;
2672
+ }
2673
+ await run2;
2674
+ }
2675
+ renameAcks = 0;
2676
+ ackBusy = 0;
2677
+ ackMs = [];
2678
+ ownLocks = /* @__PURE__ */ new Set();
2679
+ peerMoves = /* @__PURE__ */ new Map();
2680
+ peerWaits = /* @__PURE__ */ new Map();
2681
+ inflight = /* @__PURE__ */ new Set();
2682
+ renameCall;
2683
+ looking;
2684
+ lookedAt = Number.NEGATIVE_INFINITY;
2685
+ peerWaited = 0;
2686
+ queries = 0;
2687
+ renameWaits() {
2688
+ return {
2689
+ acks: this.renameAcks,
2690
+ busy: this.ackBusy,
2691
+ waits: this.peerWaited,
2692
+ queries: this.queries,
2693
+ ackMs: this.ackMs
2694
+ };
2695
+ }
2696
+ notePeer(lock, tree2) {
2697
+ let known = this.peerMoves.get(lock);
2698
+ if (!known) {
2699
+ known = { lock, trees: [] };
2700
+ this.peerMoves.set(lock, known);
2701
+ }
2702
+ if (!known.trees.some(([a, b]) => a === tree2[0] && b === tree2[1])) known.trees.push(tree2);
2703
+ return known;
2704
+ }
2705
+ async learnRenames() {
2706
+ for (const { rename } of await this.meta.under(RENAMES)) {
2707
+ if (rename?.lock === void 0 || this.ownLocks.has(rename.lock)) continue;
2708
+ this.notePeer(rename.lock, [rename.from, rename.to]);
2709
+ }
2710
+ }
2711
+ async renamingElsewhere() {
2712
+ const { held = [] } = await this.locks.query();
2713
+ return held.some(({ name: name2 }) => name2?.startsWith(RENAME_LOCK) && !this.ownLocks.has(name2));
2714
+ }
2715
+ lookForRenames() {
2716
+ const locks = this.locks;
2717
+ const age = performance.now() - this.lookedAt;
2718
+ if (!locks || age < SHARED_TTL_MS / 2) return Promise.resolve();
2719
+ this.looking ??= (async () => {
2720
+ for (; ; ) {
2721
+ const asked = performance.now();
2722
+ this.queries++;
2723
+ const { held = [] } = await locks.query();
2724
+ const unknown = () => held.some(
2725
+ ({ name: name2 }) => name2?.startsWith(RENAME_LOCK) && !this.ownLocks.has(name2) && !this.peerMoves.has(name2)
2726
+ );
2727
+ if (unknown()) await this.learnRenames();
2728
+ if (!unknown()) {
2729
+ this.lookedAt = asked;
2730
+ return;
2731
+ }
2732
+ await new Promise((resolve2) => setTimeout(resolve2, INTENT_POLL_MS));
2733
+ }
2734
+ })().finally(() => {
2735
+ this.looking = void 0;
2736
+ });
2737
+ if (age >= SHARED_TTL_MS) return this.looking;
2738
+ this.looking.catch(nothing);
2739
+ return Promise.resolve();
2740
+ }
2741
+ async heardRename({ from, to, lock }) {
2742
+ if (this.ownLocks.has(lock)) return;
2743
+ this.notePeer(lock, [from, to]);
2744
+ this.lookedAt = performance.now();
2745
+ const trees = [[from, to]];
2746
+ const running = [...this.inflight];
2747
+ const reached = await Promise.all(
2748
+ running.map(async (op) => {
2749
+ if (op.paths.some((at) => touches(at, trees))) return true;
2750
+ for (const at of op.paths) {
2751
+ const forms = await this.resolvedForms(at);
2752
+ if (forms.some((form) => touches(form, trees))) return true;
2753
+ }
2754
+ return false;
2755
+ })
2756
+ );
2757
+ const busy = running.filter((op, i) => reached[i] && this.inflight.has(op));
2758
+ if (this.aloneCalls > 0) {
2759
+ this.aloneDrain ??= Promise.withResolvers();
2760
+ busy.push({ paths: [], waiting: this.aloneDrain, release: nothing });
2761
+ }
2762
+ await Promise.all(
2763
+ busy.map((op) => {
2764
+ op.waiting ??= Promise.withResolvers();
2765
+ return op.waiting.promise;
2766
+ })
2767
+ );
2768
+ }
2769
+ peerAt(at, listing) {
2770
+ for (const move of this.peerMoves.values()) {
2771
+ if (this.ownLocks.has(move.lock)) continue;
2772
+ if (touches(at, move.trees, listing)) return move;
2773
+ }
2774
+ return void 0;
2775
+ }
2776
+ clashing(from, to) {
2777
+ for (const move of this.peerMoves.values()) {
2778
+ for (const tree2 of move.trees) {
2779
+ for (const mine of [from, to]) {
2780
+ if (tree2.some((theirs) => within2(mine, theirs) || within2(theirs, mine))) return move;
2781
+ }
2782
+ }
2783
+ }
2784
+ return void 0;
2785
+ }
2786
+ peerMoved(move) {
2787
+ const locks = this.locks;
2788
+ let wait2 = this.peerWaits.get(move.lock);
2789
+ if (wait2 !== void 0) return wait2;
2790
+ wait2 = (async () => {
2791
+ if (!move.done)
2792
+ await locks.request(move.lock, async () => {
2793
+ if (move.done) return;
2794
+ const left = (await this.meta.under(RENAMES)).filter((entry) => entry.rename?.lock === move.lock).sort(byIntent);
2795
+ for (const entry of left) await this.finishRename(entry);
2796
+ });
2797
+ this.peerMoves.delete(move.lock);
2798
+ for (const [from, to] of move.trees) {
2799
+ this.forget(from);
2800
+ this.forget(to);
2801
+ this.meta.forgetTrees?.([from, to]);
2802
+ }
2803
+ })().finally(() => this.peerWaits.delete(move.lock));
2804
+ this.peerWaits.set(move.lock, wait2);
2805
+ return wait2;
2806
+ }
2807
+ async peerSettled(at, listing) {
2808
+ await this.lookForRenames();
2809
+ for (let reals; ; ) {
2810
+ const forms = [at, ...reals ?? []];
2811
+ const move = forms.map((form) => this.peerAt(form, listing)).find(Boolean);
2812
+ if (move) {
2813
+ this.peerWaited++;
2814
+ await this.peerMoved(move);
2815
+ continue;
2816
+ }
2817
+ if (reals !== void 0 || at === "/" || this.peerMoves.size === 0) return forms;
2818
+ reals = await this.resolvedForms(at);
2819
+ if (reals.length === 0) return forms;
2820
+ }
2821
+ }
2822
+ aloneCalls = 0;
2823
+ aloneDrain;
2824
+ async alonePlain(inner, args) {
2825
+ this.aloneCalls++;
2826
+ try {
2827
+ return await inner.apply(this, args);
2828
+ } finally {
2829
+ this.aloneCalls--;
2830
+ if (this.aloneCalls === 0 && this.aloneDrain) {
2831
+ this.aloneDrain.resolve();
2832
+ this.aloneDrain = void 0;
2833
+ }
2834
+ }
2835
+ }
2836
+ async resolvedForms(at) {
2837
+ if (at === "/") return [];
2838
+ const parent = await PLAIN.realpath.call(this, parentOf3(at), true).catch(() => parentOf3(at));
2839
+ const named = resolvePath(parent, baseOf(at));
2840
+ const target = await PLAIN.realpath.call(this, at, true).catch(() => named);
2841
+ return [.../* @__PURE__ */ new Set([named, target])].filter((form) => form !== at);
2842
+ }
2843
+ amongPeers() {
2844
+ return this.remote !== void 0 && this.locks !== void 0 && this.shared();
2845
+ }
2846
+ async peerOp(name2, args, inner) {
2847
+ const paths = (name2 === "symlink" ? args.slice(1, 2) : PAIRED.has(name2) ? args.slice(0, 2) : args.slice(0, 1)).filter((path) => typeof path === "string").map(normalizePath);
2848
+ const listing = LISTINGS.has(name2);
2849
+ const checked = [...paths];
2850
+ while (this.amongPeers()) {
2851
+ checked.length = 0;
2852
+ for (const at of paths) checked.push(...await this.peerSettled(at, listing));
2853
+ if (!checked.some((at) => this.peerAt(at, listing))) break;
2854
+ }
2855
+ const busy = { paths: checked, release: () => void 0 };
2856
+ const done5 = () => {
2857
+ this.inflight.delete(busy);
2858
+ busy.waiting?.resolve();
2859
+ };
2860
+ busy.release = done5;
2861
+ this.inflight.add(busy);
2862
+ if (name2 === "rename") this.renameCall = busy;
2863
+ if (name2 !== "writeSession") return await inner.apply(this, args).finally(done5);
2864
+ const session = await inner.apply(this, args).catch((err) => {
2865
+ done5();
2866
+ throw err;
2867
+ });
2868
+ return { ...session, close: () => session.close().finally(done5) };
2869
+ }
2440
2870
  renamingDirs = /* @__PURE__ */ new Set();
2441
2871
  intents = 0;
2442
2872
  async intend(rename, at = `${RENAMES}/${crypto.randomUUID()}`) {
@@ -2482,7 +2912,7 @@ var OpfsFs = class {
2482
2912
  );
2483
2913
  for (const entry of intents) {
2484
2914
  const lock = entry.rename.lock;
2485
- if (!orphaned || !locks || !lock) await this.finishRename(entry);
2915
+ if (!locks || !lock) await this.finishRename(entry);
2486
2916
  else await locks.request(lock, () => this.finishRename(entry));
2487
2917
  }
2488
2918
  return intents.length;
@@ -2498,7 +2928,8 @@ var OpfsFs = class {
2498
2928
  this.forget(to);
2499
2929
  try {
2500
2930
  const left = await this.real(from);
2501
- if (left && replace && await this.real(to)) await this.rm(to, { recursive: true });
2931
+ if (left && replace && await this.real(to))
2932
+ await PLAIN.rm.call(this, to, { recursive: true });
2502
2933
  if (left) {
2503
2934
  const source = await this.locate(from, false);
2504
2935
  const target = await this.locate(parentOf3(to), true);
@@ -2512,7 +2943,7 @@ var OpfsFs = class {
2512
2943
  this.forget(from);
2513
2944
  this.forget(to);
2514
2945
  }
2515
- if ((await this.meta.under(from)).length > 0) {
2946
+ if ((await this.meta.under(from)).some((entry2) => !entry2.vacated)) {
2516
2947
  await this.meta.move(
2517
2948
  from,
2518
2949
  to,
@@ -2843,6 +3274,21 @@ var OpfsFs = class {
2843
3274
  return removed;
2844
3275
  }
2845
3276
  };
3277
+ function nothing() {
3278
+ return void 0;
3279
+ }
3280
+ function touches(at, trees, listing = true) {
3281
+ return trees.some(
3282
+ ([from, to]) => within2(at, from) || within2(at, to) || listing && (at === parentOf3(from) || at === parentOf3(to))
3283
+ );
3284
+ }
3285
+ function byIntent(a, b) {
3286
+ return a.rename.at - b.rename.at;
3287
+ }
3288
+ var PLAIN = {
3289
+ rm: OpfsFs.prototype.rm,
3290
+ realpath: OpfsFs.prototype.realpath
3291
+ };
2846
3292
  var READS = [
2847
3293
  "stat",
2848
3294
  "lstat",
@@ -2873,6 +3319,14 @@ var WRITES = [
2873
3319
  "lutimes",
2874
3320
  "chown"
2875
3321
  ];
3322
+ {
3323
+ const proto = OpfsFs.prototype;
3324
+ const inner = proto.createExclusive;
3325
+ proto.createExclusive = function(...args) {
3326
+ if (this.amongPeers()) return this.peerOp("createExclusive", args, inner);
3327
+ return this.alonePlain(inner, args);
3328
+ };
3329
+ }
2876
3330
  for (const [names2, op] of [
2877
3331
  [READS, "read"],
2878
3332
  [WRITES, "write"]
@@ -2883,7 +3337,9 @@ for (const [names2, op] of [
2883
3337
  proto[name2] = async function(...args) {
2884
3338
  try {
2885
3339
  if (op === "read") await this.settledRename(args[0]);
2886
- return await inner.apply(this, args);
3340
+ if (this.amongPeers() || name2 === "writeSession")
3341
+ return await this.peerOp(name2, args, inner);
3342
+ return await this.alonePlain(inner, args);
2887
3343
  } catch (err) {
2888
3344
  throw translate(err, String(args[0]), op, false);
2889
3345
  }
@@ -7128,7 +7584,7 @@ function satisfies(version, range2) {
7128
7584
  }
7129
7585
 
7130
7586
  // src/version.ts
7131
- var KERNEL_VERSION = true ? "1.141.3" : "0.0.0-dev";
7587
+ var KERNEL_VERSION = true ? "1.142.1" : "0.0.0-dev";
7132
7588
  var OS_RELEASE = "6.0.0-slicc";
7133
7589
  function procVersion() {
7134
7590
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9505,89 +9961,6 @@ var FileWatches = class {
9505
9961
  }
9506
9962
  };
9507
9963
 
9508
- // src/fs/changes.ts
9509
- var GEN = 0;
9510
- var RING = 1;
9511
- var RING_SIZE = 4096;
9512
- var SLOT = 2;
9513
- var DIRTY = RING + RING_SIZE * SLOT;
9514
- var FOREIGN = DIRTY + 1;
9515
- var CHANGES_BYTES = (FOREIGN + 1) * 4;
9516
- function changeHash(path) {
9517
- let h = 2166136261;
9518
- for (let i = 0; i < path.length; i++) {
9519
- h ^= path.charCodeAt(i);
9520
- h = Math.imul(h, 16777619);
9521
- }
9522
- return h === 0 ? 1 : h;
9523
- }
9524
- function normal(path) {
9525
- const parts2 = [];
9526
- for (const part of path.split("/")) {
9527
- if (part === "" || part === ".") continue;
9528
- if (part === "..") parts2.pop();
9529
- else parts2.push(part);
9530
- }
9531
- return `/${parts2.join("/")}`;
9532
- }
9533
- var FileChanges = class {
9534
- sab;
9535
- view;
9536
- constructor(sab = new SharedArrayBuffer(CHANGES_BYTES)) {
9537
- this.sab = sab;
9538
- this.view = new Int32Array(sab);
9539
- }
9540
- dirtied(delta) {
9541
- Atomics.add(this.view, DIRTY, delta);
9542
- }
9543
- foreign(on) {
9544
- Atomics.store(this.view, FOREIGN, on ? 1 : 0);
9545
- }
9546
- flush() {
9547
- Atomics.add(this.view, GEN, RING_SIZE + 1);
9548
- }
9549
- publish(...paths) {
9550
- for (const path of paths) {
9551
- const g = Atomics.add(this.view, GEN, 1) + 1 | 0;
9552
- const at = RING + (g & RING_SIZE - 1) * SLOT;
9553
- Atomics.store(this.view, at, 0);
9554
- Atomics.store(this.view, at + 1, changeHash(normal(path)));
9555
- Atomics.store(this.view, at, g);
9556
- }
9557
- }
9558
- };
9559
- var ChangeView = class {
9560
- view;
9561
- constructor(sab) {
9562
- this.view = new Int32Array(sab);
9563
- }
9564
- now() {
9565
- return Atomics.load(this.view, GEN);
9566
- }
9567
- clean() {
9568
- return this.view.length > DIRTY && Atomics.load(this.view, DIRTY) === 0;
9569
- }
9570
- foreign() {
9571
- return this.view.length > FOREIGN && Atomics.load(this.view, FOREIGN) === 1;
9572
- }
9573
- changedSince(seen, path) {
9574
- const now = Atomics.load(this.view, GEN);
9575
- const distance = now - seen >>> 0;
9576
- if (distance === 0) return { now, changed: false };
9577
- if (distance > RING_SIZE) return { now, changed: true };
9578
- const hash = changeHash(normal(path));
9579
- for (let i = 1; i <= distance; i++) {
9580
- const g = seen + i | 0;
9581
- const at = RING + (g & RING_SIZE - 1) * SLOT;
9582
- if (Atomics.load(this.view, at) !== g) return { now, changed: true };
9583
- const hit = Atomics.load(this.view, at + 1) === hash;
9584
- if (Atomics.load(this.view, at) !== g) return { now, changed: true };
9585
- if (hit) return { now, changed: true };
9586
- }
9587
- return { now, changed: false };
9588
- }
9589
- };
9590
-
9591
9964
  // src/fs/commands.ts
9592
9965
  var S_IFDIR2 = 16384;
9593
9966
  var S_IFREG2 = 32768;
@@ -10016,10 +10389,10 @@ var FreshModules = class {
10016
10389
  const ranged2 = rangedOps(fs);
10017
10390
  if (ranged2) {
10018
10391
  overrides.pwrite = async (path, at, bytes, transfer) => {
10019
- const touches2 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10392
+ const touches3 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10020
10393
  const first = at === 0 && bytes.length >= MAGIC.length ? bytes.slice(0, MAGIC.length) : void 0;
10021
10394
  await ranged2.pwrite(path, at, bytes, transfer);
10022
- if (touches2) await decide(path, first);
10395
+ if (touches3) await decide(path, first);
10023
10396
  };
10024
10397
  overrides.truncate = async (path, size) => {
10025
10398
  await ranged2.truncate(path, size);
@@ -10096,7 +10469,7 @@ var baseOf3 = (path) => path.slice(path.lastIndexOf("/") + 1);
10096
10469
  var WATCH_BATCH_LIMIT = 1e3;
10097
10470
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
10098
10471
  var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
10099
- function touches(watcher, changed) {
10472
+ function touches2(watcher, changed) {
10100
10473
  return watcher.paths.some(
10101
10474
  (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf7(changed) === watched)
10102
10475
  );
@@ -10224,7 +10597,7 @@ var FsWatchers = class {
10224
10597
  for (const watcher of this.watchers) {
10225
10598
  for (const path of paths) {
10226
10599
  const changed = normalizePath(path);
10227
- if (touches(watcher, changed)) watcher.pending.add(changed);
10600
+ if (touches2(watcher, changed)) watcher.pending.add(changed);
10228
10601
  }
10229
10602
  if (watcher.pending.size === 0 || watcher.scheduled) continue;
10230
10603
  watcher.scheduled = true;
@@ -13889,7 +14262,18 @@ var SYS_TREE = {
13889
14262
  slicc_opfs_ops: (listing) => `${listing.opfsOps ?? 0}
13890
14263
  `,
13891
14264
  slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
13892
- `
14265
+ `,
14266
+ slicc_rename_ack_busy: (listing) => `${listing.renameAcks?.busy ?? 0}
14267
+ `,
14268
+ slicc_rename_acks: (listing) => {
14269
+ const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
14270
+ const median = samples[Math.floor(samples.length / 2)] ?? 0;
14271
+ const most = samples.at(-1) ?? 0;
14272
+ const waits = listing.renameAcks?.waits ?? 0;
14273
+ const queries = listing.renameAcks?.queries ?? 0;
14274
+ return `${listing.renameAcks?.acks ?? 0} ${median.toFixed(1)} ${most.toFixed(1)} ${waits} ${queries}
14275
+ `;
14276
+ }
13893
14277
  }
13894
14278
  };
13895
14279
  function isSysFile(at) {
@@ -19721,7 +20105,7 @@ function sniffPg(start2) {
19721
20105
  const length2 = int32(bytes, 0);
19722
20106
  return length2 >= 8 && length2 <= PG_MESSAGE_MAX && int32(bytes, 4) === PROTOCOL_3 ? "yes" : "no";
19723
20107
  }
19724
- var PLAIN = "SCRAM-SHA-256";
20108
+ var PLAIN2 = "SCRAM-SHA-256";
19725
20109
  var PLUS = "SCRAM-SHA-256-PLUS";
19726
20110
  var END_POINT = "p=tls-server-end-point,,";
19727
20111
  var SASL = 10;
@@ -19843,7 +20227,7 @@ function pgLength(bytes, started) {
19843
20227
  const length2 = int32(bytes, started ? 1 : 0);
19844
20228
  return started ? 1 + length2 : length2;
19845
20229
  }
19846
- var MECHANISMS = /* @__PURE__ */ new Set([PLAIN, PLUS]);
20230
+ var MECHANISMS = /* @__PURE__ */ new Set([PLAIN2, PLUS]);
19847
20231
  async function cleartext(message3, asks) {
19848
20232
  const body = latin2(message3.subarray(5));
19849
20233
  if (!body.endsWith("\0") || body.indexOf("\0") !== body.length - 1)
@@ -19876,7 +20260,7 @@ function serverHeader(header4, server) {
19876
20260
  function rewritten(first, header4) {
19877
20261
  const parsed2 = clientFirstOf(first.data);
19878
20262
  if (!parsed2 || parsed2.header === header4) return first;
19879
- return { mechanism: header4 === END_POINT ? PLUS : PLAIN, data: `${header4}${parsed2.bare}` };
20263
+ return { mechanism: header4 === END_POINT ? PLUS : PLAIN2, data: `${header4}${parsed2.bare}` };
19880
20264
  }
19881
20265
  var PgClient = class {
19882
20266
  clientFirst;
@@ -27494,7 +27878,7 @@ var RULE = new RegExp(
27494
27878
  String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?(?:Cwd\s+(${WORD})\s+)?Cmnd(?:\s+(.*))?$`
27495
27879
  );
27496
27880
  var TERM = /^[A-Za-z0-9._+-]+$/;
27497
- var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27881
+ var PLAIN3 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27498
27882
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
27499
27883
  function failed2(errno3, error) {
27500
27884
  return { error, errno: errno3 };
@@ -27521,7 +27905,7 @@ function splitWords(text) {
27521
27905
  return words2;
27522
27906
  }
27523
27907
  function quoteWord(word) {
27524
- return PLAIN2.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27908
+ return PLAIN3.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27525
27909
  }
27526
27910
  function validWho(who) {
27527
27911
  if (who === "ALL") return true;
@@ -29334,7 +29718,8 @@ var Launcher = class {
29334
29718
  fsCalls: servedFsCalls(),
29335
29719
  opfsCalls: this.rootFs.opfsCalls?.() ?? 0,
29336
29720
  opfsOps: this.rootFs.opfsOps?.() ?? 0,
29337
- cwdChecks: this.cwds.checks
29721
+ cwdChecks: this.cwds.checks,
29722
+ renameAcks: this.rootFs.renameWaits?.()
29338
29723
  }),
29339
29724
  sudo: (call, signal) => this.sudo(pid, call, signal),
29340
29725
  crontab: (call) => this.crontab(pid, call),