@ai-ecoverse/slicc-kernel 1.142.0 → 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.
package/dist/node.js CHANGED
@@ -3624,6 +3624,90 @@ function serveClient(port, host) {
3624
3624
  return { detach, closed: ended.promise };
3625
3625
  }
3626
3626
 
3627
+ // src/fs/changes.ts
3628
+ var GEN = 0;
3629
+ var RING = 1;
3630
+ var RING_SIZE = 4096;
3631
+ var SLOT = 2;
3632
+ var SHARED_TTL_MS = 100;
3633
+ var DIRTY = RING + RING_SIZE * SLOT;
3634
+ var FOREIGN = DIRTY + 1;
3635
+ var CHANGES_BYTES = (FOREIGN + 1) * 4;
3636
+ function changeHash(path) {
3637
+ let h = 2166136261;
3638
+ for (let i = 0; i < path.length; i++) {
3639
+ h ^= path.charCodeAt(i);
3640
+ h = Math.imul(h, 16777619);
3641
+ }
3642
+ return h === 0 ? 1 : h;
3643
+ }
3644
+ function normal(path) {
3645
+ const parts2 = [];
3646
+ for (const part of path.split("/")) {
3647
+ if (part === "" || part === ".") continue;
3648
+ if (part === "..") parts2.pop();
3649
+ else parts2.push(part);
3650
+ }
3651
+ return `/${parts2.join("/")}`;
3652
+ }
3653
+ var FileChanges = class {
3654
+ sab;
3655
+ view;
3656
+ constructor(sab = new SharedArrayBuffer(CHANGES_BYTES)) {
3657
+ this.sab = sab;
3658
+ this.view = new Int32Array(sab);
3659
+ }
3660
+ dirtied(delta) {
3661
+ Atomics.add(this.view, DIRTY, delta);
3662
+ }
3663
+ foreign(on) {
3664
+ Atomics.store(this.view, FOREIGN, on ? 1 : 0);
3665
+ }
3666
+ flush() {
3667
+ Atomics.add(this.view, GEN, RING_SIZE + 1);
3668
+ }
3669
+ publish(...paths) {
3670
+ for (const path of paths) {
3671
+ const g = Atomics.add(this.view, GEN, 1) + 1 | 0;
3672
+ const at = RING + (g & RING_SIZE - 1) * SLOT;
3673
+ Atomics.store(this.view, at, 0);
3674
+ Atomics.store(this.view, at + 1, changeHash(normal(path)));
3675
+ Atomics.store(this.view, at, g);
3676
+ }
3677
+ }
3678
+ };
3679
+ var ChangeView = class {
3680
+ view;
3681
+ constructor(sab) {
3682
+ this.view = new Int32Array(sab);
3683
+ }
3684
+ now() {
3685
+ return Atomics.load(this.view, GEN);
3686
+ }
3687
+ clean() {
3688
+ return this.view.length > DIRTY && Atomics.load(this.view, DIRTY) === 0;
3689
+ }
3690
+ foreign() {
3691
+ return this.view.length > FOREIGN && Atomics.load(this.view, FOREIGN) === 1;
3692
+ }
3693
+ changedSince(seen, path) {
3694
+ const now = Atomics.load(this.view, GEN);
3695
+ const distance = now - seen >>> 0;
3696
+ if (distance === 0) return { now, changed: false };
3697
+ if (distance > RING_SIZE) return { now, changed: true };
3698
+ const hash = changeHash(normal(path));
3699
+ for (let i = 1; i <= distance; i++) {
3700
+ const g = seen + i | 0;
3701
+ const at = RING + (g & RING_SIZE - 1) * SLOT;
3702
+ if (Atomics.load(this.view, at) !== g) return { now, changed: true };
3703
+ const hit = Atomics.load(this.view, at + 1) === hash;
3704
+ if (Atomics.load(this.view, at) !== g) return { now, changed: true };
3705
+ if (hit) return { now, changed: true };
3706
+ }
3707
+ return { now, changed: false };
3708
+ }
3709
+ };
3710
+
3627
3711
  // src/fs/path-map.ts
3628
3712
  function parentOf2(path) {
3629
3713
  if (path === "/" || path === "") return void 0;
@@ -4158,6 +4242,8 @@ var ANNOUNCE_WAIT_MS = 500;
4158
4242
  var PRUNE_EVERY_MS = 3e4;
4159
4243
  var MOURN_TRIES = 5;
4160
4244
  var MOURN_RETRY_MS = 100;
4245
+ var RENAME_ACK_MAX_MS = 1e4;
4246
+ var ANNOUNCE_POLL_MS = 20;
4161
4247
  function lockManager() {
4162
4248
  return globalThis.navigator?.locks;
4163
4249
  }
@@ -4181,6 +4267,9 @@ var RemoteWatch = class {
4181
4267
  quiet;
4182
4268
  crowd;
4183
4269
  gone;
4270
+ renaming;
4271
+ renamed;
4272
+ ackers = /* @__PURE__ */ new Set();
4184
4273
  told;
4185
4274
  counted = 0;
4186
4275
  recount() {
@@ -4208,7 +4297,7 @@ var RemoteWatch = class {
4208
4297
  this.settled = true;
4209
4298
  this.recount();
4210
4299
  });
4211
- this.channel.postMessage({ hello: this.tab });
4300
+ this.channel.postMessage({ hello: this.tab, renames: true });
4212
4301
  }
4213
4302
  async join() {
4214
4303
  if (!this.locks) return;
@@ -4229,6 +4318,20 @@ var RemoteWatch = class {
4229
4318
  await this.joined;
4230
4319
  return this.peers.size > 0;
4231
4320
  }
4321
+ async joinedAll(cap) {
4322
+ if (!this.locks) return true;
4323
+ const prefix = `${this.name}:`;
4324
+ const began = performance.now();
4325
+ for (; ; ) {
4326
+ const held = ((await this.locks.query()).held ?? []).map((lock) => lock.name ?? "");
4327
+ const silent = held.some(
4328
+ (name2) => name2.startsWith(prefix) && name2 !== this.lockOf(this.tab) && !this.peers.has(name2.slice(prefix.length))
4329
+ );
4330
+ if (!silent) return true;
4331
+ if (performance.now() - began >= cap) return false;
4332
+ await new Promise((resolve2) => setTimeout(resolve2, ANNOUNCE_POLL_MS));
4333
+ }
4334
+ }
4232
4335
  crowded() {
4233
4336
  return !this.settled || this.peers.size > 0;
4234
4337
  }
@@ -4236,7 +4339,11 @@ var RemoteWatch = class {
4236
4339
  return `${this.name}:${tab}`;
4237
4340
  }
4238
4341
  receive(data) {
4342
+ const sender = data.hello ?? data.here;
4343
+ if (sender !== void 0 && data.renames) this.ackers.add(sender);
4239
4344
  if (data.hello !== void 0) this.greeted(data.hello);
4345
+ if (data.renaming !== void 0 && data.tab !== void 0) this.heard(data);
4346
+ if (data.renamed !== void 0) this.renamed?.(data.renamed);
4240
4347
  if (data.here !== void 0 && data.to === this.tab) {
4241
4348
  this.arrived(data.here);
4242
4349
  this.met?.();
@@ -4268,6 +4375,7 @@ var RemoteWatch = class {
4268
4375
  this.mourned.delete(peer);
4269
4376
  this.others.delete(peer);
4270
4377
  this.peers.delete(peer);
4378
+ this.ackers.delete(peer);
4271
4379
  this.rebuild();
4272
4380
  this.recount();
4273
4381
  this.gone?.();
@@ -4279,12 +4387,18 @@ var RemoteWatch = class {
4279
4387
  greeted(peer) {
4280
4388
  this.arrived(peer);
4281
4389
  const answer2 = () => {
4282
- this.channel.postMessage({ here: this.tab, to: peer });
4390
+ this.channel.postMessage({ here: this.tab, to: peer, renames: true });
4283
4391
  if (this.mine.length > 0) void this.announce(this.mine);
4284
4392
  };
4285
4393
  if (this.quiet) void this.quiet().then(answer2, answer2);
4286
4394
  else answer2();
4287
4395
  }
4396
+ heard({ tab, renaming, seq: seq2 }) {
4397
+ const ack = () => this.channel.postMessage({ ack: seq2, from: this.tab, to: tab });
4398
+ const heard = this.renaming?.(renaming);
4399
+ if (heard !== void 0) void heard.then(ack, ack);
4400
+ else ack();
4401
+ }
4288
4402
  announced({ tab, paths, seq: seq2 }) {
4289
4403
  const from = tab;
4290
4404
  this.arrived(from);
@@ -4311,6 +4425,7 @@ var RemoteWatch = class {
4311
4425
  if (!held.has(this.lockOf(tab))) {
4312
4426
  if (this.peers.delete(tab)) left = true;
4313
4427
  this.others.delete(tab);
4428
+ this.ackers.delete(tab);
4314
4429
  }
4315
4430
  }
4316
4431
  this.rebuild();
@@ -4332,7 +4447,34 @@ var RemoteWatch = class {
4332
4447
  this.mine = [...paths];
4333
4448
  const seq2 = ++this.seq;
4334
4449
  this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
4335
- if (this.peers.size === 0) return Promise.resolve();
4450
+ return this.acks(seq2, this.peers);
4451
+ }
4452
+ renamingTree(rename, cap = RENAME_ACK_MAX_MS) {
4453
+ const seq2 = ++this.seq;
4454
+ this.channel.postMessage({ tab: this.tab, renaming: rename, seq: seq2 });
4455
+ const left = new Set(this.ackers);
4456
+ if (left.size === 0) return Promise.resolve([]);
4457
+ const began = performance.now();
4458
+ return new Promise((resolve2) => {
4459
+ const done4 = () => {
4460
+ clearInterval(timer);
4461
+ this.waiting.delete(seq2);
4462
+ resolve2([...left]);
4463
+ };
4464
+ const timer = setInterval(() => {
4465
+ void this.prune().then(() => {
4466
+ for (const peer of left) if (!this.ackers.has(peer)) left.delete(peer);
4467
+ if (left.size === 0 || performance.now() - began >= cap) done4();
4468
+ });
4469
+ }, ANNOUNCE_WAIT_MS);
4470
+ this.waiting.set(seq2, { left, done: done4 });
4471
+ });
4472
+ }
4473
+ renamedTree(lock) {
4474
+ this.channel.postMessage({ renamed: lock });
4475
+ }
4476
+ acks(seq2, from) {
4477
+ if (from.size === 0) return Promise.resolve();
4336
4478
  return new Promise((resolve2) => {
4337
4479
  const done4 = () => {
4338
4480
  clearTimeout(timer);
@@ -4343,7 +4485,7 @@ var RemoteWatch = class {
4343
4485
  done4();
4344
4486
  void this.prune();
4345
4487
  }, ANNOUNCE_WAIT_MS);
4346
- this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
4488
+ this.waiting.set(seq2, { left: new Set(from), done: done4 });
4347
4489
  });
4348
4490
  }
4349
4491
  emit(change) {
@@ -4406,6 +4548,12 @@ var RECONCILE_LISTINGS = 8;
4406
4548
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
4407
4549
  var RENAMES = "/.slicc-renames";
4408
4550
  var RENAME_LOCK = "slicc-rename:";
4551
+ var RENAME_ADMIT = "slicc-rename-admit:";
4552
+ var PAIRED = /* @__PURE__ */ new Set(["rename", "link", "copyFile"]);
4553
+ var LISTINGS = /* @__PURE__ */ new Set(["readdir", "readdirStat"]);
4554
+ var RETRY = /* @__PURE__ */ Symbol("retry");
4555
+ var ACK_SAMPLES = 4096;
4556
+ var INTENT_POLL_MS = 5;
4409
4557
  function movable(handle) {
4410
4558
  return typeof handle.move === "function";
4411
4559
  }
@@ -4450,6 +4598,7 @@ function sidecar(meta) {
4450
4598
  for (const { path, change } of changes) await ops.update(path, change);
4451
4599
  };
4452
4600
  if (meta.absent) ops.absent = meta.absent.bind(meta);
4601
+ if (meta.forgetTrees) ops.forgetTrees = meta.forgetTrees.bind(meta);
4453
4602
  const { removeFile } = meta;
4454
4603
  if (removeFile)
4455
4604
  ops.removeFile = (path) => removeFile.call(meta, path).catch((err) => {
@@ -4571,7 +4720,17 @@ var OpfsFs = class {
4571
4720
  this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`, { locks: this.locks ?? null });
4572
4721
  if (this.remote) {
4573
4722
  this.remote.quiet = () => this.quiet().then(() => this.settle?.());
4574
- this.remote.gone = () => void this.orphans();
4723
+ this.remote.gone = () => {
4724
+ void this.orphans();
4725
+ for (const move of [...this.peerMoves.values()]) void this.peerMoved(move);
4726
+ };
4727
+ this.remote.renaming = (rename) => this.heardRename(rename);
4728
+ this.remote.renamed = (lock) => {
4729
+ const move = this.peerMoves.get(lock);
4730
+ if (!move) return;
4731
+ move.done = true;
4732
+ void this.peerMoved(move);
4733
+ };
4575
4734
  }
4576
4735
  if (this.channel) {
4577
4736
  const remote = this.remote;
@@ -5615,16 +5774,27 @@ var OpfsFs = class {
5615
5774
  throw fsError("EPERM", `${from}: this file system has no hard links`);
5616
5775
  }
5617
5776
  async rename(from, to) {
5777
+ const call = this.renameCall;
5778
+ this.renameCall = void 0;
5618
5779
  if (from === to) return;
5780
+ for (; ; ) {
5781
+ try {
5782
+ return await this.renameOnce(from, to, call);
5783
+ } catch (err) {
5784
+ if (err !== RETRY) throw err;
5785
+ }
5786
+ }
5787
+ }
5788
+ async renameOnce(from, to, call) {
5619
5789
  const source = await this.locate(from, false);
5620
5790
  this.refuse(source.path, true);
5621
5791
  this.forget(source.path);
5622
5792
  const parent = await this.locate(parentOf4(to), true);
5623
5793
  const destination = resolvePath(parent.path, baseOf(to));
5624
5794
  this.refuse(destination, true);
5625
- await this.entering(destination, () => this.moveTo(source, parent, destination, to));
5795
+ await this.entering(destination, () => this.moveTo(source, parent, destination, to, call));
5626
5796
  }
5627
- async moveTo(source, parent, destination, to) {
5797
+ async moveTo(source, parent, destination, to, call) {
5628
5798
  const sourceStat = await this.describe(source);
5629
5799
  if (sourceStat.isDirectory && within3(destination, source.path)) throw fsError("EINVAL", to);
5630
5800
  const target = await this.exactly(() => this.locate(destination, false)).catch((err) => {
@@ -5659,6 +5829,7 @@ var OpfsFs = class {
5659
5829
  const name2 = baseOf(destination);
5660
5830
  const swap = targetStat?.isDirectory === true;
5661
5831
  let lock;
5832
+ const hooks = {};
5662
5833
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
5663
5834
  const copying = sourceStat.isDirectory && !movable(source.handle);
5664
5835
  const staged = resolvePath(parent.path, staging);
@@ -5667,8 +5838,15 @@ var OpfsFs = class {
5667
5838
  const copies = /* @__PURE__ */ new Map();
5668
5839
  const root = { ino: inodeOf(source.path), ...moving };
5669
5840
  const owned4 = lock ? { lock } : {};
5841
+ hooks.notify?.();
5670
5842
  const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
5671
5843
  const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
5844
+ await hooks.started?.().catch(async (err) => {
5845
+ for (const intent of [first, second]) {
5846
+ if (intent) await this.meta.update(intent, () => void 0);
5847
+ }
5848
+ throw err;
5849
+ });
5672
5850
  const moved2 = await this.replacingTarget(
5673
5851
  targetStat !== null,
5674
5852
  () => this.place(source.handle, dir, staging, copies)
@@ -5703,13 +5881,262 @@ var OpfsFs = class {
5703
5881
  const locks = this.locks;
5704
5882
  if (locks) {
5705
5883
  lock = `${RENAME_LOCK}${crypto.randomUUID()}`;
5706
- await locks.request(lock, place);
5884
+ this.ownLocks.add(lock);
5885
+ call?.release();
5886
+ await this.admitted(locks, lock, source.path, destination, place, hooks);
5707
5887
  } else await place();
5708
5888
  } finally {
5889
+ if (lock) this.ownLocks.delete(lock);
5709
5890
  this.renamingDirs.delete(entry);
5710
5891
  resolve2();
5711
5892
  }
5712
5893
  }
5894
+ async admitted(locks, lock, from, to, place, hooks) {
5895
+ const remote = this.remote;
5896
+ if (remote && !this.shared() && !await remote.joinedAll(RENAME_ACK_MAX_MS)) {
5897
+ this.ackBusy++;
5898
+ console.warn(
5899
+ `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)`
5900
+ );
5901
+ throw fsError("EBUSY", from);
5902
+ }
5903
+ if (!remote || !this.shared()) {
5904
+ await locks.request(lock, place);
5905
+ return;
5906
+ }
5907
+ let clash;
5908
+ let run2;
5909
+ await locks.request(`${RENAME_ADMIT}${this.rootName ?? ""}`, async () => {
5910
+ if (await this.renamingElsewhere()) await this.learnRenames();
5911
+ clash = this.clashing(from, to);
5912
+ if (clash) return;
5913
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
5914
+ let told;
5915
+ hooks.notify = () => {
5916
+ told = remote.renamingTree({ from, to, lock });
5917
+ };
5918
+ hooks.started = async () => {
5919
+ resolve2();
5920
+ this.renameAcks++;
5921
+ const at = performance.now();
5922
+ const stuck = await told;
5923
+ this.ackMs.push(performance.now() - at);
5924
+ if (this.ackMs.length > ACK_SAMPLES) this.ackMs.shift();
5925
+ if (stuck.length === 0) return;
5926
+ this.ackBusy++;
5927
+ console.warn(
5928
+ `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)`
5929
+ );
5930
+ throw fsError("EBUSY", from);
5931
+ };
5932
+ run2 = locks.request(lock, async () => {
5933
+ await place();
5934
+ remote.renamedTree(lock);
5935
+ });
5936
+ await Promise.race([promise, run2.then(nothing, nothing)]);
5937
+ });
5938
+ if (clash) {
5939
+ this.peerWaited++;
5940
+ await this.peerMoved(clash);
5941
+ throw RETRY;
5942
+ }
5943
+ await run2;
5944
+ }
5945
+ renameAcks = 0;
5946
+ ackBusy = 0;
5947
+ ackMs = [];
5948
+ ownLocks = /* @__PURE__ */ new Set();
5949
+ peerMoves = /* @__PURE__ */ new Map();
5950
+ peerWaits = /* @__PURE__ */ new Map();
5951
+ inflight = /* @__PURE__ */ new Set();
5952
+ renameCall;
5953
+ looking;
5954
+ lookedAt = Number.NEGATIVE_INFINITY;
5955
+ peerWaited = 0;
5956
+ queries = 0;
5957
+ renameWaits() {
5958
+ return {
5959
+ acks: this.renameAcks,
5960
+ busy: this.ackBusy,
5961
+ waits: this.peerWaited,
5962
+ queries: this.queries,
5963
+ ackMs: this.ackMs
5964
+ };
5965
+ }
5966
+ notePeer(lock, tree2) {
5967
+ let known = this.peerMoves.get(lock);
5968
+ if (!known) {
5969
+ known = { lock, trees: [] };
5970
+ this.peerMoves.set(lock, known);
5971
+ }
5972
+ if (!known.trees.some(([a, b]) => a === tree2[0] && b === tree2[1])) known.trees.push(tree2);
5973
+ return known;
5974
+ }
5975
+ async learnRenames() {
5976
+ for (const { rename } of await this.meta.under(RENAMES)) {
5977
+ if (rename?.lock === void 0 || this.ownLocks.has(rename.lock)) continue;
5978
+ this.notePeer(rename.lock, [rename.from, rename.to]);
5979
+ }
5980
+ }
5981
+ async renamingElsewhere() {
5982
+ const { held = [] } = await this.locks.query();
5983
+ return held.some(({ name: name2 }) => name2?.startsWith(RENAME_LOCK) && !this.ownLocks.has(name2));
5984
+ }
5985
+ lookForRenames() {
5986
+ const locks = this.locks;
5987
+ const age = performance.now() - this.lookedAt;
5988
+ if (!locks || age < SHARED_TTL_MS / 2) return Promise.resolve();
5989
+ this.looking ??= (async () => {
5990
+ for (; ; ) {
5991
+ const asked = performance.now();
5992
+ this.queries++;
5993
+ const { held = [] } = await locks.query();
5994
+ const unknown = () => held.some(
5995
+ ({ name: name2 }) => name2?.startsWith(RENAME_LOCK) && !this.ownLocks.has(name2) && !this.peerMoves.has(name2)
5996
+ );
5997
+ if (unknown()) await this.learnRenames();
5998
+ if (!unknown()) {
5999
+ this.lookedAt = asked;
6000
+ return;
6001
+ }
6002
+ await new Promise((resolve2) => setTimeout(resolve2, INTENT_POLL_MS));
6003
+ }
6004
+ })().finally(() => {
6005
+ this.looking = void 0;
6006
+ });
6007
+ if (age >= SHARED_TTL_MS) return this.looking;
6008
+ this.looking.catch(nothing);
6009
+ return Promise.resolve();
6010
+ }
6011
+ async heardRename({ from, to, lock }) {
6012
+ if (this.ownLocks.has(lock)) return;
6013
+ this.notePeer(lock, [from, to]);
6014
+ this.lookedAt = performance.now();
6015
+ const trees = [[from, to]];
6016
+ const running = [...this.inflight];
6017
+ const reached = await Promise.all(
6018
+ running.map(async (op) => {
6019
+ if (op.paths.some((at) => touches(at, trees))) return true;
6020
+ for (const at of op.paths) {
6021
+ const forms = await this.resolvedForms(at);
6022
+ if (forms.some((form) => touches(form, trees))) return true;
6023
+ }
6024
+ return false;
6025
+ })
6026
+ );
6027
+ const busy = running.filter((op, i) => reached[i] && this.inflight.has(op));
6028
+ if (this.aloneCalls > 0) {
6029
+ this.aloneDrain ??= Promise.withResolvers();
6030
+ busy.push({ paths: [], waiting: this.aloneDrain, release: nothing });
6031
+ }
6032
+ await Promise.all(
6033
+ busy.map((op) => {
6034
+ op.waiting ??= Promise.withResolvers();
6035
+ return op.waiting.promise;
6036
+ })
6037
+ );
6038
+ }
6039
+ peerAt(at, listing) {
6040
+ for (const move of this.peerMoves.values()) {
6041
+ if (this.ownLocks.has(move.lock)) continue;
6042
+ if (touches(at, move.trees, listing)) return move;
6043
+ }
6044
+ return void 0;
6045
+ }
6046
+ clashing(from, to) {
6047
+ for (const move of this.peerMoves.values()) {
6048
+ for (const tree2 of move.trees) {
6049
+ for (const mine of [from, to]) {
6050
+ if (tree2.some((theirs) => within3(mine, theirs) || within3(theirs, mine))) return move;
6051
+ }
6052
+ }
6053
+ }
6054
+ return void 0;
6055
+ }
6056
+ peerMoved(move) {
6057
+ const locks = this.locks;
6058
+ let wait2 = this.peerWaits.get(move.lock);
6059
+ if (wait2 !== void 0) return wait2;
6060
+ wait2 = (async () => {
6061
+ if (!move.done)
6062
+ await locks.request(move.lock, async () => {
6063
+ if (move.done) return;
6064
+ const left = (await this.meta.under(RENAMES)).filter((entry) => entry.rename?.lock === move.lock).sort(byIntent);
6065
+ for (const entry of left) await this.finishRename(entry);
6066
+ });
6067
+ this.peerMoves.delete(move.lock);
6068
+ for (const [from, to] of move.trees) {
6069
+ this.forget(from);
6070
+ this.forget(to);
6071
+ this.meta.forgetTrees?.([from, to]);
6072
+ }
6073
+ })().finally(() => this.peerWaits.delete(move.lock));
6074
+ this.peerWaits.set(move.lock, wait2);
6075
+ return wait2;
6076
+ }
6077
+ async peerSettled(at, listing) {
6078
+ await this.lookForRenames();
6079
+ for (let reals; ; ) {
6080
+ const forms = [at, ...reals ?? []];
6081
+ const move = forms.map((form) => this.peerAt(form, listing)).find(Boolean);
6082
+ if (move) {
6083
+ this.peerWaited++;
6084
+ await this.peerMoved(move);
6085
+ continue;
6086
+ }
6087
+ if (reals !== void 0 || at === "/" || this.peerMoves.size === 0) return forms;
6088
+ reals = await this.resolvedForms(at);
6089
+ if (reals.length === 0) return forms;
6090
+ }
6091
+ }
6092
+ aloneCalls = 0;
6093
+ aloneDrain;
6094
+ async alonePlain(inner, args) {
6095
+ this.aloneCalls++;
6096
+ try {
6097
+ return await inner.apply(this, args);
6098
+ } finally {
6099
+ this.aloneCalls--;
6100
+ if (this.aloneCalls === 0 && this.aloneDrain) {
6101
+ this.aloneDrain.resolve();
6102
+ this.aloneDrain = void 0;
6103
+ }
6104
+ }
6105
+ }
6106
+ async resolvedForms(at) {
6107
+ if (at === "/") return [];
6108
+ const parent = await PLAIN.realpath.call(this, parentOf4(at), true).catch(() => parentOf4(at));
6109
+ const named = resolvePath(parent, baseOf(at));
6110
+ const target = await PLAIN.realpath.call(this, at, true).catch(() => named);
6111
+ return [.../* @__PURE__ */ new Set([named, target])].filter((form) => form !== at);
6112
+ }
6113
+ amongPeers() {
6114
+ return this.remote !== void 0 && this.locks !== void 0 && this.shared();
6115
+ }
6116
+ async peerOp(name2, args, inner) {
6117
+ 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);
6118
+ const listing = LISTINGS.has(name2);
6119
+ const checked = [...paths];
6120
+ while (this.amongPeers()) {
6121
+ checked.length = 0;
6122
+ for (const at of paths) checked.push(...await this.peerSettled(at, listing));
6123
+ if (!checked.some((at) => this.peerAt(at, listing))) break;
6124
+ }
6125
+ const busy = { paths: checked, release: () => void 0 };
6126
+ const done4 = () => {
6127
+ this.inflight.delete(busy);
6128
+ busy.waiting?.resolve();
6129
+ };
6130
+ busy.release = done4;
6131
+ this.inflight.add(busy);
6132
+ if (name2 === "rename") this.renameCall = busy;
6133
+ if (name2 !== "writeSession") return await inner.apply(this, args).finally(done4);
6134
+ const session = await inner.apply(this, args).catch((err) => {
6135
+ done4();
6136
+ throw err;
6137
+ });
6138
+ return { ...session, close: () => session.close().finally(done4) };
6139
+ }
5713
6140
  renamingDirs = /* @__PURE__ */ new Set();
5714
6141
  intents = 0;
5715
6142
  async intend(rename, at = `${RENAMES}/${crypto.randomUUID()}`) {
@@ -5755,7 +6182,7 @@ var OpfsFs = class {
5755
6182
  );
5756
6183
  for (const entry of intents) {
5757
6184
  const lock = entry.rename.lock;
5758
- if (!orphaned || !locks || !lock) await this.finishRename(entry);
6185
+ if (!locks || !lock) await this.finishRename(entry);
5759
6186
  else await locks.request(lock, () => this.finishRename(entry));
5760
6187
  }
5761
6188
  return intents.length;
@@ -5771,7 +6198,8 @@ var OpfsFs = class {
5771
6198
  this.forget(to);
5772
6199
  try {
5773
6200
  const left = await this.real(from);
5774
- if (left && replace && await this.real(to)) await this.rm(to, { recursive: true });
6201
+ if (left && replace && await this.real(to))
6202
+ await PLAIN.rm.call(this, to, { recursive: true });
5775
6203
  if (left) {
5776
6204
  const source = await this.locate(from, false);
5777
6205
  const target = await this.locate(parentOf4(to), true);
@@ -5785,7 +6213,7 @@ var OpfsFs = class {
5785
6213
  this.forget(from);
5786
6214
  this.forget(to);
5787
6215
  }
5788
- if ((await this.meta.under(from)).length > 0) {
6216
+ if ((await this.meta.under(from)).some((entry2) => !entry2.vacated)) {
5789
6217
  await this.meta.move(
5790
6218
  from,
5791
6219
  to,
@@ -6116,6 +6544,21 @@ var OpfsFs = class {
6116
6544
  return removed;
6117
6545
  }
6118
6546
  };
6547
+ function nothing() {
6548
+ return void 0;
6549
+ }
6550
+ function touches(at, trees, listing = true) {
6551
+ return trees.some(
6552
+ ([from, to]) => within3(at, from) || within3(at, to) || listing && (at === parentOf4(from) || at === parentOf4(to))
6553
+ );
6554
+ }
6555
+ function byIntent(a, b) {
6556
+ return a.rename.at - b.rename.at;
6557
+ }
6558
+ var PLAIN = {
6559
+ rm: OpfsFs.prototype.rm,
6560
+ realpath: OpfsFs.prototype.realpath
6561
+ };
6119
6562
  var READS = [
6120
6563
  "stat",
6121
6564
  "lstat",
@@ -6146,6 +6589,14 @@ var WRITES = [
6146
6589
  "lutimes",
6147
6590
  "chown"
6148
6591
  ];
6592
+ {
6593
+ const proto = OpfsFs.prototype;
6594
+ const inner = proto.createExclusive;
6595
+ proto.createExclusive = function(...args) {
6596
+ if (this.amongPeers()) return this.peerOp("createExclusive", args, inner);
6597
+ return this.alonePlain(inner, args);
6598
+ };
6599
+ }
6149
6600
  for (const [names2, op] of [
6150
6601
  [READS, "read"],
6151
6602
  [WRITES, "write"]
@@ -6156,7 +6607,9 @@ for (const [names2, op] of [
6156
6607
  proto[name2] = async function(...args) {
6157
6608
  try {
6158
6609
  if (op === "read") await this.settledRename(args[0]);
6159
- return await inner.apply(this, args);
6610
+ if (this.amongPeers() || name2 === "writeSession")
6611
+ return await this.peerOp(name2, args, inner);
6612
+ return await this.alonePlain(inner, args);
6160
6613
  } catch (err) {
6161
6614
  throw translate(err, String(args[0]), op, false);
6162
6615
  }
@@ -7515,7 +7968,7 @@ function satisfies(version, range2) {
7515
7968
  }
7516
7969
 
7517
7970
  // src/version.ts
7518
- var KERNEL_VERSION = true ? "1.142.0" : "0.0.0-dev";
7971
+ var KERNEL_VERSION = true ? "1.142.1" : "0.0.0-dev";
7519
7972
  var OS_RELEASE = "6.0.0-slicc";
7520
7973
  function procVersion() {
7521
7974
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9514,89 +9967,6 @@ var FileWatches = class {
9514
9967
  }
9515
9968
  };
9516
9969
 
9517
- // src/fs/changes.ts
9518
- var GEN = 0;
9519
- var RING = 1;
9520
- var RING_SIZE = 4096;
9521
- var SLOT = 2;
9522
- var DIRTY = RING + RING_SIZE * SLOT;
9523
- var FOREIGN = DIRTY + 1;
9524
- var CHANGES_BYTES = (FOREIGN + 1) * 4;
9525
- function changeHash(path) {
9526
- let h = 2166136261;
9527
- for (let i = 0; i < path.length; i++) {
9528
- h ^= path.charCodeAt(i);
9529
- h = Math.imul(h, 16777619);
9530
- }
9531
- return h === 0 ? 1 : h;
9532
- }
9533
- function normal(path) {
9534
- const parts2 = [];
9535
- for (const part of path.split("/")) {
9536
- if (part === "" || part === ".") continue;
9537
- if (part === "..") parts2.pop();
9538
- else parts2.push(part);
9539
- }
9540
- return `/${parts2.join("/")}`;
9541
- }
9542
- var FileChanges = class {
9543
- sab;
9544
- view;
9545
- constructor(sab = new SharedArrayBuffer(CHANGES_BYTES)) {
9546
- this.sab = sab;
9547
- this.view = new Int32Array(sab);
9548
- }
9549
- dirtied(delta) {
9550
- Atomics.add(this.view, DIRTY, delta);
9551
- }
9552
- foreign(on) {
9553
- Atomics.store(this.view, FOREIGN, on ? 1 : 0);
9554
- }
9555
- flush() {
9556
- Atomics.add(this.view, GEN, RING_SIZE + 1);
9557
- }
9558
- publish(...paths) {
9559
- for (const path of paths) {
9560
- const g = Atomics.add(this.view, GEN, 1) + 1 | 0;
9561
- const at = RING + (g & RING_SIZE - 1) * SLOT;
9562
- Atomics.store(this.view, at, 0);
9563
- Atomics.store(this.view, at + 1, changeHash(normal(path)));
9564
- Atomics.store(this.view, at, g);
9565
- }
9566
- }
9567
- };
9568
- var ChangeView = class {
9569
- view;
9570
- constructor(sab) {
9571
- this.view = new Int32Array(sab);
9572
- }
9573
- now() {
9574
- return Atomics.load(this.view, GEN);
9575
- }
9576
- clean() {
9577
- return this.view.length > DIRTY && Atomics.load(this.view, DIRTY) === 0;
9578
- }
9579
- foreign() {
9580
- return this.view.length > FOREIGN && Atomics.load(this.view, FOREIGN) === 1;
9581
- }
9582
- changedSince(seen, path) {
9583
- const now = Atomics.load(this.view, GEN);
9584
- const distance = now - seen >>> 0;
9585
- if (distance === 0) return { now, changed: false };
9586
- if (distance > RING_SIZE) return { now, changed: true };
9587
- const hash = changeHash(normal(path));
9588
- for (let i = 1; i <= distance; i++) {
9589
- const g = seen + i | 0;
9590
- const at = RING + (g & RING_SIZE - 1) * SLOT;
9591
- if (Atomics.load(this.view, at) !== g) return { now, changed: true };
9592
- const hit = Atomics.load(this.view, at + 1) === hash;
9593
- if (Atomics.load(this.view, at) !== g) return { now, changed: true };
9594
- if (hit) return { now, changed: true };
9595
- }
9596
- return { now, changed: false };
9597
- }
9598
- };
9599
-
9600
9970
  // src/fs/commands.ts
9601
9971
  var S_IFDIR2 = 16384;
9602
9972
  var S_IFREG2 = 32768;
@@ -10025,10 +10395,10 @@ var FreshModules = class {
10025
10395
  const ranged2 = rangedOps(fs);
10026
10396
  if (ranged2) {
10027
10397
  overrides.pwrite = async (path, at, bytes, transfer) => {
10028
- const touches2 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10398
+ const touches3 = at < MAGIC.length && this.fresh.has(normalizePath(path));
10029
10399
  const first = at === 0 && bytes.length >= MAGIC.length ? bytes.slice(0, MAGIC.length) : void 0;
10030
10400
  await ranged2.pwrite(path, at, bytes, transfer);
10031
- if (touches2) await decide(path, first);
10401
+ if (touches3) await decide(path, first);
10032
10402
  };
10033
10403
  overrides.truncate = async (path, size) => {
10034
10404
  await ranged2.truncate(path, size);
@@ -10105,7 +10475,7 @@ var baseOf3 = (path) => path.slice(path.lastIndexOf("/") + 1);
10105
10475
  var WATCH_BATCH_LIMIT = 1e3;
10106
10476
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
10107
10477
  var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
10108
- function touches(watcher, changed) {
10478
+ function touches2(watcher, changed) {
10109
10479
  return watcher.paths.some(
10110
10480
  (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf7(changed) === watched)
10111
10481
  );
@@ -10233,7 +10603,7 @@ var FsWatchers = class {
10233
10603
  for (const watcher of this.watchers) {
10234
10604
  for (const path of paths) {
10235
10605
  const changed = normalizePath(path);
10236
- if (touches(watcher, changed)) watcher.pending.add(changed);
10606
+ if (touches2(watcher, changed)) watcher.pending.add(changed);
10237
10607
  }
10238
10608
  if (watcher.pending.size === 0 || watcher.scheduled) continue;
10239
10609
  watcher.scheduled = true;
@@ -13898,7 +14268,18 @@ var SYS_TREE = {
13898
14268
  slicc_opfs_ops: (listing) => `${listing.opfsOps ?? 0}
13899
14269
  `,
13900
14270
  slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
13901
- `
14271
+ `,
14272
+ slicc_rename_ack_busy: (listing) => `${listing.renameAcks?.busy ?? 0}
14273
+ `,
14274
+ slicc_rename_acks: (listing) => {
14275
+ const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
14276
+ const median = samples[Math.floor(samples.length / 2)] ?? 0;
14277
+ const most = samples.at(-1) ?? 0;
14278
+ const waits = listing.renameAcks?.waits ?? 0;
14279
+ const queries = listing.renameAcks?.queries ?? 0;
14280
+ return `${listing.renameAcks?.acks ?? 0} ${median.toFixed(1)} ${most.toFixed(1)} ${waits} ${queries}
14281
+ `;
14282
+ }
13902
14283
  }
13903
14284
  };
13904
14285
  function isSysFile(at) {
@@ -19570,7 +19951,7 @@ function sniffPg(start2) {
19570
19951
  const length2 = int32(bytes, 0);
19571
19952
  return length2 >= 8 && length2 <= PG_MESSAGE_MAX && int32(bytes, 4) === PROTOCOL_3 ? "yes" : "no";
19572
19953
  }
19573
- var PLAIN = "SCRAM-SHA-256";
19954
+ var PLAIN2 = "SCRAM-SHA-256";
19574
19955
  var PLUS = "SCRAM-SHA-256-PLUS";
19575
19956
  var END_POINT = "p=tls-server-end-point,,";
19576
19957
  var SASL = 10;
@@ -19692,7 +20073,7 @@ function pgLength(bytes, started) {
19692
20073
  const length2 = int32(bytes, started ? 1 : 0);
19693
20074
  return started ? 1 + length2 : length2;
19694
20075
  }
19695
- var MECHANISMS = /* @__PURE__ */ new Set([PLAIN, PLUS]);
20076
+ var MECHANISMS = /* @__PURE__ */ new Set([PLAIN2, PLUS]);
19696
20077
  async function cleartext(message3, asks) {
19697
20078
  const body = latin2(message3.subarray(5));
19698
20079
  if (!body.endsWith("\0") || body.indexOf("\0") !== body.length - 1)
@@ -19725,7 +20106,7 @@ function serverHeader(header5, server) {
19725
20106
  function rewritten(first, header5) {
19726
20107
  const parsed2 = clientFirstOf(first.data);
19727
20108
  if (!parsed2 || parsed2.header === header5) return first;
19728
- return { mechanism: header5 === END_POINT ? PLUS : PLAIN, data: `${header5}${parsed2.bare}` };
20109
+ return { mechanism: header5 === END_POINT ? PLUS : PLAIN2, data: `${header5}${parsed2.bare}` };
19729
20110
  }
19730
20111
  var PgClient = class {
19731
20112
  clientFirst;
@@ -27415,7 +27796,7 @@ var RULE = new RegExp(
27415
27796
  String.raw`^(NOPASSWD\s+)?(\S+)\s+(?:\(([^()\s]+)\)\s+)?(?:Cwd\s+(${WORD})\s+)?Cmnd(?:\s+(.*))?$`
27416
27797
  );
27417
27798
  var TERM = /^[A-Za-z0-9._+-]+$/;
27418
- var PLAIN2 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27799
+ var PLAIN3 = /^[A-Za-z0-9_@%+=:,./-]+$/;
27419
27800
  var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
27420
27801
  function failed2(errno3, error) {
27421
27802
  return { error, errno: errno3 };
@@ -27442,7 +27823,7 @@ function splitWords(text) {
27442
27823
  return words2;
27443
27824
  }
27444
27825
  function quoteWord(word) {
27445
- return PLAIN2.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27826
+ return PLAIN3.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
27446
27827
  }
27447
27828
  function validWho(who) {
27448
27829
  if (who === "ALL") return true;
@@ -29251,7 +29632,8 @@ var Launcher = class {
29251
29632
  fsCalls: servedFsCalls(),
29252
29633
  opfsCalls: this.rootFs.opfsCalls?.() ?? 0,
29253
29634
  opfsOps: this.rootFs.opfsOps?.() ?? 0,
29254
- cwdChecks: this.cwds.checks
29635
+ cwdChecks: this.cwds.checks,
29636
+ renameAcks: this.rootFs.renameWaits?.()
29255
29637
  }),
29256
29638
  sudo: (call, signal) => this.sudo(pid, call, signal),
29257
29639
  crontab: (call) => this.crontab(pid, call),