@ai-ecoverse/slicc-kernel 1.142.0 → 1.142.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.
@@ -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,14 @@ 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 escapeRoot = (name2) => name2.replace(/[%:]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
1279
+ var LEGACY_RENAME_LOCK = /^slicc-rename:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1280
+ var PAIRED = /* @__PURE__ */ new Set(["rename", "link", "copyFile"]);
1281
+ var LISTINGS = /* @__PURE__ */ new Set(["readdir", "readdirStat"]);
1282
+ var RETRY = /* @__PURE__ */ Symbol("retry");
1283
+ var ACK_SAMPLES = 4096;
1284
+ var INTENT_POLL_MS = 5;
1132
1285
  function movable(handle) {
1133
1286
  return typeof handle.move === "function";
1134
1287
  }
@@ -1173,6 +1326,7 @@ function sidecar(meta) {
1173
1326
  for (const { path, change } of changes) await ops.update(path, change);
1174
1327
  };
1175
1328
  if (meta.absent) ops.absent = meta.absent.bind(meta);
1329
+ if (meta.forgetTrees) ops.forgetTrees = meta.forgetTrees.bind(meta);
1176
1330
  const { removeFile } = meta;
1177
1331
  if (removeFile)
1178
1332
  ops.removeFile = (path) => removeFile.call(meta, path).catch((err) => {
@@ -1287,6 +1441,7 @@ var OpfsFs = class {
1287
1441
  locks;
1288
1442
  lockPrefix;
1289
1443
  rootName;
1444
+ renamePrefix;
1290
1445
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
1291
1446
  this.root = root;
1292
1447
  this.meta = sidecar(meta);
@@ -1294,11 +1449,22 @@ var OpfsFs = class {
1294
1449
  this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
1295
1450
  this.lockPrefix = `slicc-excl:${channel ?? ""}:`;
1296
1451
  this.rootName = channel;
1452
+ this.renamePrefix = `${RENAME_LOCK}${escapeRoot(channel ?? "")}:`;
1297
1453
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
1298
1454
  this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`, { locks: this.locks ?? null });
1299
1455
  if (this.remote) {
1300
1456
  this.remote.quiet = () => this.quiet().then(() => this.settle?.());
1301
- this.remote.gone = () => void this.orphans();
1457
+ this.remote.gone = () => {
1458
+ void this.orphans();
1459
+ for (const move of [...this.peerMoves.values()]) void this.peerMoved(move);
1460
+ };
1461
+ this.remote.renaming = (rename) => this.heardRename(rename);
1462
+ this.remote.renamed = (lock) => {
1463
+ const move = this.peerMoves.get(lock);
1464
+ if (!move) return;
1465
+ move.done = true;
1466
+ void this.peerMoved(move);
1467
+ };
1302
1468
  }
1303
1469
  if (this.channel) {
1304
1470
  const remote = this.remote;
@@ -2342,16 +2508,27 @@ var OpfsFs = class {
2342
2508
  throw fsError("EPERM", `${from}: this file system has no hard links`);
2343
2509
  }
2344
2510
  async rename(from, to) {
2511
+ const call = this.renameCall;
2512
+ this.renameCall = void 0;
2345
2513
  if (from === to) return;
2514
+ for (; ; ) {
2515
+ try {
2516
+ return await this.renameOnce(from, to, call);
2517
+ } catch (err) {
2518
+ if (err !== RETRY) throw err;
2519
+ }
2520
+ }
2521
+ }
2522
+ async renameOnce(from, to, call) {
2346
2523
  const source = await this.locate(from, false);
2347
2524
  this.refuse(source.path, true);
2348
2525
  this.forget(source.path);
2349
2526
  const parent = await this.locate(parentOf3(to), true);
2350
2527
  const destination = resolvePath(parent.path, baseOf(to));
2351
2528
  this.refuse(destination, true);
2352
- await this.entering(destination, () => this.moveTo(source, parent, destination, to));
2529
+ await this.entering(destination, () => this.moveTo(source, parent, destination, to, call));
2353
2530
  }
2354
- async moveTo(source, parent, destination, to) {
2531
+ async moveTo(source, parent, destination, to, call) {
2355
2532
  const sourceStat = await this.describe(source);
2356
2533
  if (sourceStat.isDirectory && within2(destination, source.path)) throw fsError("EINVAL", to);
2357
2534
  const target = await this.exactly(() => this.locate(destination, false)).catch((err) => {
@@ -2386,6 +2563,7 @@ var OpfsFs = class {
2386
2563
  const name2 = baseOf(destination);
2387
2564
  const swap = targetStat?.isDirectory === true;
2388
2565
  let lock;
2566
+ const hooks = {};
2389
2567
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
2390
2568
  const copying = sourceStat.isDirectory && !movable(source.handle);
2391
2569
  const staged = resolvePath(parent.path, staging);
@@ -2394,8 +2572,15 @@ var OpfsFs = class {
2394
2572
  const copies = /* @__PURE__ */ new Map();
2395
2573
  const root = { ino: inodeOf(source.path), ...moving };
2396
2574
  const owned4 = lock ? { lock } : {};
2575
+ hooks.notify?.();
2397
2576
  const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
2398
2577
  const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
2578
+ await hooks.started?.().catch(async (err) => {
2579
+ for (const intent of [first, second]) {
2580
+ if (intent) await this.meta.update(intent, () => void 0);
2581
+ }
2582
+ throw err;
2583
+ });
2399
2584
  const moved2 = await this.replacingTarget(
2400
2585
  targetStat !== null,
2401
2586
  () => this.place(source.handle, dir, staging, copies)
@@ -2429,14 +2614,270 @@ var OpfsFs = class {
2429
2614
  try {
2430
2615
  const locks = this.locks;
2431
2616
  if (locks) {
2432
- lock = `${RENAME_LOCK}${crypto.randomUUID()}`;
2433
- await locks.request(lock, place);
2617
+ lock = `${this.renameLocks()}${crypto.randomUUID()}`;
2618
+ this.ownLocks.add(lock);
2619
+ call?.release();
2620
+ await this.admitted(locks, lock, source.path, destination, place, hooks);
2434
2621
  } else await place();
2435
2622
  } finally {
2623
+ if (lock) this.ownLocks.delete(lock);
2436
2624
  this.renamingDirs.delete(entry);
2437
2625
  resolve2();
2438
2626
  }
2439
2627
  }
2628
+ async admitted(locks, lock, from, to, place, hooks) {
2629
+ const remote = this.remote;
2630
+ if (remote && !this.shared() && !await remote.joinedAll(RENAME_ACK_MAX_MS)) {
2631
+ this.ackBusy++;
2632
+ console.warn(
2633
+ `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)`
2634
+ );
2635
+ throw fsError("EBUSY", from);
2636
+ }
2637
+ if (!remote || !this.shared()) {
2638
+ await locks.request(lock, place);
2639
+ return;
2640
+ }
2641
+ let clash;
2642
+ let run2;
2643
+ await locks.request(`${RENAME_ADMIT}${this.rootName ?? ""}`, async () => {
2644
+ if (await this.renamingElsewhere()) await this.learnRenames();
2645
+ clash = this.clashing(from, to);
2646
+ if (clash) return;
2647
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
2648
+ let told;
2649
+ hooks.notify = () => {
2650
+ told = remote.renamingTree({ from, to, lock });
2651
+ };
2652
+ hooks.started = async () => {
2653
+ resolve2();
2654
+ this.renameAcks++;
2655
+ const at = performance.now();
2656
+ const stuck = await told;
2657
+ this.ackMs.push(performance.now() - at);
2658
+ if (this.ackMs.length > ACK_SAMPLES) this.ackMs.shift();
2659
+ if (stuck.length === 0) return;
2660
+ this.ackBusy++;
2661
+ console.warn(
2662
+ `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)`
2663
+ );
2664
+ throw fsError("EBUSY", from);
2665
+ };
2666
+ run2 = locks.request(lock, async () => {
2667
+ await place();
2668
+ remote.renamedTree(lock);
2669
+ });
2670
+ await Promise.race([promise, run2.then(nothing, nothing)]);
2671
+ });
2672
+ if (clash) {
2673
+ this.peerWaited++;
2674
+ await this.peerMoved(clash);
2675
+ throw RETRY;
2676
+ }
2677
+ await run2;
2678
+ }
2679
+ renameAcks = 0;
2680
+ ackBusy = 0;
2681
+ ackMs = [];
2682
+ ownLocks = /* @__PURE__ */ new Set();
2683
+ peerMoves = /* @__PURE__ */ new Map();
2684
+ peerWaits = /* @__PURE__ */ new Map();
2685
+ inflight = /* @__PURE__ */ new Set();
2686
+ renameCall;
2687
+ looking;
2688
+ lookedAt = Number.NEGATIVE_INFINITY;
2689
+ peerWaited = 0;
2690
+ queries = 0;
2691
+ renameWaits() {
2692
+ return {
2693
+ acks: this.renameAcks,
2694
+ busy: this.ackBusy,
2695
+ waits: this.peerWaited,
2696
+ queries: this.queries,
2697
+ ackMs: this.ackMs
2698
+ };
2699
+ }
2700
+ notePeer(lock, tree2) {
2701
+ let known = this.peerMoves.get(lock);
2702
+ if (!known) {
2703
+ known = { lock, trees: [] };
2704
+ this.peerMoves.set(lock, known);
2705
+ }
2706
+ if (!known.trees.some(([a, b]) => a === tree2[0] && b === tree2[1])) known.trees.push(tree2);
2707
+ return known;
2708
+ }
2709
+ renameLocks() {
2710
+ return this.renamePrefix;
2711
+ }
2712
+ renameHere(name2) {
2713
+ if (name2 === void 0) return false;
2714
+ return name2.startsWith(this.renameLocks()) || LEGACY_RENAME_LOCK.test(name2);
2715
+ }
2716
+ async learnRenames() {
2717
+ for (const { rename } of await this.meta.under(RENAMES)) {
2718
+ if (rename?.lock === void 0 || this.ownLocks.has(rename.lock)) continue;
2719
+ this.notePeer(rename.lock, [rename.from, rename.to]);
2720
+ }
2721
+ }
2722
+ async renamingElsewhere() {
2723
+ const { held = [] } = await this.locks.query();
2724
+ return held.some(({ name: name2 }) => this.renameHere(name2) && !this.ownLocks.has(name2));
2725
+ }
2726
+ lookForRenames() {
2727
+ const locks = this.locks;
2728
+ const age = performance.now() - this.lookedAt;
2729
+ if (!locks || age < SHARED_TTL_MS / 2) return Promise.resolve();
2730
+ this.looking ??= (async () => {
2731
+ for (; ; ) {
2732
+ const asked = performance.now();
2733
+ this.queries++;
2734
+ const { held = [] } = await locks.query();
2735
+ const unknown = () => held.some(
2736
+ ({ name: name2 }) => this.renameHere(name2) && !this.ownLocks.has(name2) && !this.peerMoves.has(name2)
2737
+ );
2738
+ if (unknown()) await this.learnRenames();
2739
+ if (!unknown()) {
2740
+ this.lookedAt = asked;
2741
+ return;
2742
+ }
2743
+ await new Promise((resolve2) => setTimeout(resolve2, INTENT_POLL_MS));
2744
+ }
2745
+ })().finally(() => {
2746
+ this.looking = void 0;
2747
+ });
2748
+ if (age >= SHARED_TTL_MS) return this.looking;
2749
+ this.looking.catch(nothing);
2750
+ return Promise.resolve();
2751
+ }
2752
+ async heardRename({ from, to, lock }) {
2753
+ if (this.ownLocks.has(lock)) return;
2754
+ this.notePeer(lock, [from, to]);
2755
+ this.lookedAt = performance.now();
2756
+ const trees = [[from, to]];
2757
+ const running = [...this.inflight];
2758
+ const reached = await Promise.all(
2759
+ running.map(async (op) => {
2760
+ if (op.paths.some((at) => touches(at, trees))) return true;
2761
+ for (const at of op.paths) {
2762
+ const forms = await this.resolvedForms(at);
2763
+ if (forms.some((form) => touches(form, trees))) return true;
2764
+ }
2765
+ return false;
2766
+ })
2767
+ );
2768
+ const busy = running.filter((op, i) => reached[i] && this.inflight.has(op));
2769
+ if (this.aloneCalls > 0) {
2770
+ this.aloneDrain ??= Promise.withResolvers();
2771
+ busy.push({ paths: [], waiting: this.aloneDrain, release: nothing });
2772
+ }
2773
+ await Promise.all(
2774
+ busy.map((op) => {
2775
+ op.waiting ??= Promise.withResolvers();
2776
+ return op.waiting.promise;
2777
+ })
2778
+ );
2779
+ }
2780
+ peerAt(at, listing) {
2781
+ for (const move of this.peerMoves.values()) {
2782
+ if (this.ownLocks.has(move.lock)) continue;
2783
+ if (touches(at, move.trees, listing)) return move;
2784
+ }
2785
+ return void 0;
2786
+ }
2787
+ clashing(from, to) {
2788
+ for (const move of this.peerMoves.values()) {
2789
+ for (const tree2 of move.trees) {
2790
+ for (const mine of [from, to]) {
2791
+ if (tree2.some((theirs) => within2(mine, theirs) || within2(theirs, mine))) return move;
2792
+ }
2793
+ }
2794
+ }
2795
+ return void 0;
2796
+ }
2797
+ peerMoved(move) {
2798
+ const locks = this.locks;
2799
+ let wait2 = this.peerWaits.get(move.lock);
2800
+ if (wait2 !== void 0) return wait2;
2801
+ wait2 = (async () => {
2802
+ if (!move.done)
2803
+ await locks.request(move.lock, async () => {
2804
+ if (move.done) return;
2805
+ const left = (await this.meta.under(RENAMES)).filter((entry) => entry.rename?.lock === move.lock).sort(byIntent);
2806
+ for (const entry of left) await this.finishRename(entry);
2807
+ });
2808
+ this.peerMoves.delete(move.lock);
2809
+ for (const [from, to] of move.trees) {
2810
+ this.forget(from);
2811
+ this.forget(to);
2812
+ this.meta.forgetTrees?.([from, to]);
2813
+ }
2814
+ })().finally(() => this.peerWaits.delete(move.lock));
2815
+ this.peerWaits.set(move.lock, wait2);
2816
+ return wait2;
2817
+ }
2818
+ async peerSettled(at, listing) {
2819
+ await this.lookForRenames();
2820
+ for (let reals; ; ) {
2821
+ const forms = [at, ...reals ?? []];
2822
+ const move = forms.map((form) => this.peerAt(form, listing)).find(Boolean);
2823
+ if (move) {
2824
+ this.peerWaited++;
2825
+ await this.peerMoved(move);
2826
+ continue;
2827
+ }
2828
+ if (reals !== void 0 || at === "/" || this.peerMoves.size === 0) return forms;
2829
+ reals = await this.resolvedForms(at);
2830
+ if (reals.length === 0) return forms;
2831
+ }
2832
+ }
2833
+ aloneCalls = 0;
2834
+ aloneDrain;
2835
+ async alonePlain(inner, args) {
2836
+ this.aloneCalls++;
2837
+ try {
2838
+ return await inner.apply(this, args);
2839
+ } finally {
2840
+ this.aloneCalls--;
2841
+ if (this.aloneCalls === 0 && this.aloneDrain) {
2842
+ this.aloneDrain.resolve();
2843
+ this.aloneDrain = void 0;
2844
+ }
2845
+ }
2846
+ }
2847
+ async resolvedForms(at) {
2848
+ if (at === "/") return [];
2849
+ const parent = await PLAIN.realpath.call(this, parentOf3(at), true).catch(() => parentOf3(at));
2850
+ const named = resolvePath(parent, baseOf(at));
2851
+ const target = await PLAIN.realpath.call(this, at, true).catch(() => named);
2852
+ return [.../* @__PURE__ */ new Set([named, target])].filter((form) => form !== at);
2853
+ }
2854
+ amongPeers() {
2855
+ return this.remote !== void 0 && this.locks !== void 0 && this.shared();
2856
+ }
2857
+ async peerOp(name2, args, inner) {
2858
+ 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);
2859
+ const listing = LISTINGS.has(name2);
2860
+ const checked = [...paths];
2861
+ while (this.amongPeers()) {
2862
+ checked.length = 0;
2863
+ for (const at of paths) checked.push(...await this.peerSettled(at, listing));
2864
+ if (!checked.some((at) => this.peerAt(at, listing))) break;
2865
+ }
2866
+ const busy = { paths: checked, release: () => void 0 };
2867
+ const done5 = () => {
2868
+ this.inflight.delete(busy);
2869
+ busy.waiting?.resolve();
2870
+ };
2871
+ busy.release = done5;
2872
+ this.inflight.add(busy);
2873
+ if (name2 === "rename") this.renameCall = busy;
2874
+ if (name2 !== "writeSession") return await inner.apply(this, args).finally(done5);
2875
+ const session = await inner.apply(this, args).catch((err) => {
2876
+ done5();
2877
+ throw err;
2878
+ });
2879
+ return { ...session, close: () => session.close().finally(done5) };
2880
+ }
2440
2881
  renamingDirs = /* @__PURE__ */ new Set();
2441
2882
  intents = 0;
2442
2883
  async intend(rename, at = `${RENAMES}/${crypto.randomUUID()}`) {
@@ -2482,7 +2923,7 @@ var OpfsFs = class {
2482
2923
  );
2483
2924
  for (const entry of intents) {
2484
2925
  const lock = entry.rename.lock;
2485
- if (!orphaned || !locks || !lock) await this.finishRename(entry);
2926
+ if (!locks || !lock) await this.finishRename(entry);
2486
2927
  else await locks.request(lock, () => this.finishRename(entry));
2487
2928
  }
2488
2929
  return intents.length;
@@ -2498,7 +2939,8 @@ var OpfsFs = class {
2498
2939
  this.forget(to);
2499
2940
  try {
2500
2941
  const left = await this.real(from);
2501
- if (left && replace && await this.real(to)) await this.rm(to, { recursive: true });
2942
+ if (left && replace && await this.real(to))
2943
+ await PLAIN.rm.call(this, to, { recursive: true });
2502
2944
  if (left) {
2503
2945
  const source = await this.locate(from, false);
2504
2946
  const target = await this.locate(parentOf3(to), true);
@@ -2512,7 +2954,7 @@ var OpfsFs = class {
2512
2954
  this.forget(from);
2513
2955
  this.forget(to);
2514
2956
  }
2515
- if ((await this.meta.under(from)).length > 0) {
2957
+ if ((await this.meta.under(from)).some((entry2) => !entry2.vacated)) {
2516
2958
  await this.meta.move(
2517
2959
  from,
2518
2960
  to,
@@ -2843,6 +3285,21 @@ var OpfsFs = class {
2843
3285
  return removed;
2844
3286
  }
2845
3287
  };
3288
+ function nothing() {
3289
+ return void 0;
3290
+ }
3291
+ function touches(at, trees, listing = true) {
3292
+ return trees.some(
3293
+ ([from, to]) => within2(at, from) || within2(at, to) || listing && (at === parentOf3(from) || at === parentOf3(to))
3294
+ );
3295
+ }
3296
+ function byIntent(a, b) {
3297
+ return a.rename.at - b.rename.at;
3298
+ }
3299
+ var PLAIN = {
3300
+ rm: OpfsFs.prototype.rm,
3301
+ realpath: OpfsFs.prototype.realpath
3302
+ };
2846
3303
  var READS = [
2847
3304
  "stat",
2848
3305
  "lstat",
@@ -2873,6 +3330,14 @@ var WRITES = [
2873
3330
  "lutimes",
2874
3331
  "chown"
2875
3332
  ];
3333
+ {
3334
+ const proto = OpfsFs.prototype;
3335
+ const inner = proto.createExclusive;
3336
+ proto.createExclusive = function(...args) {
3337
+ if (this.amongPeers()) return this.peerOp("createExclusive", args, inner);
3338
+ return this.alonePlain(inner, args);
3339
+ };
3340
+ }
2876
3341
  for (const [names2, op] of [
2877
3342
  [READS, "read"],
2878
3343
  [WRITES, "write"]
@@ -2883,7 +3348,9 @@ for (const [names2, op] of [
2883
3348
  proto[name2] = async function(...args) {
2884
3349
  try {
2885
3350
  if (op === "read") await this.settledRename(args[0]);
2886
- return await inner.apply(this, args);
3351
+ if (this.amongPeers() || name2 === "writeSession")
3352
+ return await this.peerOp(name2, args, inner);
3353
+ return await this.alonePlain(inner, args);
2887
3354
  } catch (err) {
2888
3355
  throw translate(err, String(args[0]), op, false);
2889
3356
  }
@@ -7128,7 +7595,7 @@ function satisfies(version, range2) {
7128
7595
  }
7129
7596
 
7130
7597
  // src/version.ts
7131
- var KERNEL_VERSION = true ? "1.142.0" : "0.0.0-dev";
7598
+ var KERNEL_VERSION = true ? "1.142.2" : "0.0.0-dev";
7132
7599
  var OS_RELEASE = "6.0.0-slicc";
7133
7600
  function procVersion() {
7134
7601
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9505,89 +9972,6 @@ var FileWatches = class {
9505
9972
  }
9506
9973
  };
9507
9974
 
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
9975
  // src/fs/commands.ts
9592
9976
  var S_IFDIR2 = 16384;
9593
9977
  var S_IFREG2 = 32768;
@@ -10016,10 +10400,10 @@ var FreshModules = class {
10016
10400
  const ranged2 = rangedOps(fs);
10017
10401
  if (ranged2) {
10018
10402
  overrides.pwrite = async (path, at, bytes, transfer) => {
10019
- const touches2 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10403
+ const touches3 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10020
10404
  const first = at === 0 && bytes.length >= MAGIC.length ? bytes.slice(0, MAGIC.length) : void 0;
10021
10405
  await ranged2.pwrite(path, at, bytes, transfer);
10022
- if (touches2) await decide(path, first);
10406
+ if (touches3) await decide(path, first);
10023
10407
  };
10024
10408
  overrides.truncate = async (path, size) => {
10025
10409
  await ranged2.truncate(path, size);
@@ -10096,7 +10480,7 @@ var baseOf3 = (path) => path.slice(path.lastIndexOf("/") + 1);
10096
10480
  var WATCH_BATCH_LIMIT = 1e3;
10097
10481
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
10098
10482
  var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
10099
- function touches(watcher, changed) {
10483
+ function touches2(watcher, changed) {
10100
10484
  return watcher.paths.some(
10101
10485
  (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf7(changed) === watched)
10102
10486
  );
@@ -10224,7 +10608,7 @@ var FsWatchers = class {
10224
10608
  for (const watcher of this.watchers) {
10225
10609
  for (const path of paths) {
10226
10610
  const changed = normalizePath(path);
10227
- if (touches(watcher, changed)) watcher.pending.add(changed);
10611
+ if (touches2(watcher, changed)) watcher.pending.add(changed);
10228
10612
  }
10229
10613
  if (watcher.pending.size === 0 || watcher.scheduled) continue;
10230
10614
  watcher.scheduled = true;
@@ -13889,7 +14273,18 @@ var SYS_TREE = {
13889
14273
  slicc_opfs_ops: (listing) => `${listing.opfsOps ?? 0}
13890
14274
  `,
13891
14275
  slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
13892
- `
14276
+ `,
14277
+ slicc_rename_ack_busy: (listing) => `${listing.renameAcks?.busy ?? 0}
14278
+ `,
14279
+ slicc_rename_acks: (listing) => {
14280
+ const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
14281
+ const median = samples[Math.floor(samples.length / 2)] ?? 0;
14282
+ const most = samples.at(-1) ?? 0;
14283
+ const waits = listing.renameAcks?.waits ?? 0;
14284
+ const queries = listing.renameAcks?.queries ?? 0;
14285
+ return `${listing.renameAcks?.acks ?? 0} ${median.toFixed(1)} ${most.toFixed(1)} ${waits} ${queries}
14286
+ `;
14287
+ }
13893
14288
  }
13894
14289
  };
13895
14290
  function isSysFile(at) {
@@ -19721,7 +20116,7 @@ function sniffPg(start2) {
19721
20116
  const length2 = int32(bytes, 0);
19722
20117
  return length2 >= 8 && length2 <= PG_MESSAGE_MAX && int32(bytes, 4) === PROTOCOL_3 ? "yes" : "no";
19723
20118
  }
19724
- var PLAIN = "SCRAM-SHA-256";
20119
+ var PLAIN2 = "SCRAM-SHA-256";
19725
20120
  var PLUS = "SCRAM-SHA-256-PLUS";
19726
20121
  var END_POINT = "p=tls-server-end-point,,";
19727
20122
  var SASL = 10;
@@ -19843,7 +20238,7 @@ function pgLength(bytes, started) {
19843
20238
  const length2 = int32(bytes, started ? 1 : 0);
19844
20239
  return started ? 1 + length2 : length2;
19845
20240
  }
19846
- var MECHANISMS = /* @__PURE__ */ new Set([PLAIN, PLUS]);
20241
+ var MECHANISMS = /* @__PURE__ */ new Set([PLAIN2, PLUS]);
19847
20242
  async function cleartext(message3, asks) {
19848
20243
  const body = latin2(message3.subarray(5));
19849
20244
  if (!body.endsWith("\0") || body.indexOf("\0") !== body.length - 1)
@@ -19876,7 +20271,7 @@ function serverHeader(header4, server) {
19876
20271
  function rewritten(first, header4) {
19877
20272
  const parsed2 = clientFirstOf(first.data);
19878
20273
  if (!parsed2 || parsed2.header === header4) return first;
19879
- return { mechanism: header4 === END_POINT ? PLUS : PLAIN, data: `${header4}${parsed2.bare}` };
20274
+ return { mechanism: header4 === END_POINT ? PLUS : PLAIN2, data: `${header4}${parsed2.bare}` };
19880
20275
  }
19881
20276
  var PgClient = class {
19882
20277
  clientFirst;
@@ -27494,7 +27889,7 @@ var RULE = new RegExp(
27494
27889
  String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?(?:Cwd\s+(${WORD})\s+)?Cmnd(?:\s+(.*))?$`
27495
27890
  );
27496
27891
  var TERM = /^[A-Za-z0-9._+-]+$/;
27497
- var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27892
+ var PLAIN3 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27498
27893
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
27499
27894
  function failed2(errno3, error) {
27500
27895
  return { error, errno: errno3 };
@@ -27521,7 +27916,7 @@ function splitWords(text) {
27521
27916
  return words2;
27522
27917
  }
27523
27918
  function quoteWord(word) {
27524
- return PLAIN2.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27919
+ return PLAIN3.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27525
27920
  }
27526
27921
  function validWho(who) {
27527
27922
  if (who === "ALL") return true;
@@ -29334,7 +29729,8 @@ var Launcher = class {
29334
29729
  fsCalls: servedFsCalls(),
29335
29730
  opfsCalls: this.rootFs.opfsCalls?.() ?? 0,
29336
29731
  opfsOps: this.rootFs.opfsOps?.() ?? 0,
29337
- cwdChecks: this.cwds.checks
29732
+ cwdChecks: this.cwds.checks,
29733
+ renameAcks: this.rootFs.renameWaits?.()
29338
29734
  }),
29339
29735
  sudo: (call, signal) => this.sudo(pid, call, signal),
29340
29736
  crontab: (call) => this.crontab(pid, call),