@ai-ecoverse/slicc-kernel 1.54.8 → 1.54.9

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
@@ -2813,7 +2813,7 @@ var OpfsFs = class {
2813
2813
  const swap = targetStat?.isDirectory === true;
2814
2814
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
2815
2815
  this.forget(destination);
2816
- const timed = await this.explicitTimes(source.path);
2816
+ const timed2 = await this.explicitTimes(source.path);
2817
2817
  const moved2 = await this.place(source.handle, dir, staging);
2818
2818
  if (swap) await this.rm(destination, { recursive: true });
2819
2819
  await this.meta.move(source.path, destination, carry);
@@ -2825,7 +2825,7 @@ var OpfsFs = class {
2825
2825
  if (!placed) await dir.removeEntry(staging, { recursive: true });
2826
2826
  copied ||= !placed;
2827
2827
  }
2828
- if (copied) await this.restamp(destination, timed);
2828
+ if (copied) await this.restamp(destination, timed2);
2829
2829
  this.changed(source.path, destination);
2830
2830
  this.touch(parentOf2(source.path), parent.path);
2831
2831
  }
@@ -2834,16 +2834,16 @@ var OpfsFs = class {
2834
2834
  return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
2835
2835
  }
2836
2836
  async explicitTimes(path) {
2837
- const timed = [];
2837
+ const timed2 = [];
2838
2838
  for (const entry of await this.meta.under(path)) {
2839
2839
  if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
2840
2840
  const current = await this.modified(entry.path).catch(() => void 0);
2841
- if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
2841
+ if (current === entry.mtimeFor) timed2.push(entry.path.slice(path.length));
2842
2842
  }
2843
- return timed;
2843
+ return timed2;
2844
2844
  }
2845
- async restamp(path, timed) {
2846
- for (const rest2 of timed) {
2845
+ async restamp(path, timed2) {
2846
+ for (const rest2 of timed2) {
2847
2847
  const mtimeFor = await this.modified(path + rest2);
2848
2848
  await this.meta.update(path + rest2, (entry) => entry && { ...entry, mtimeFor });
2849
2849
  }
@@ -11573,11 +11573,24 @@ var HostfsClient = class {
11573
11573
  get maxIo() {
11574
11574
  return this.capabilities.maxIo ?? HOSTFS_IO;
11575
11575
  }
11576
- async send(path, init, handle = false, retried = false) {
11576
+ retired;
11577
+ stop = new AbortController();
11578
+ get gone() {
11579
+ return this.retired !== void 0;
11580
+ }
11581
+ retire(why) {
11582
+ this.retired ??= why;
11583
+ this.stop.abort();
11584
+ }
11585
+ async send(path, init, handle = false, retried = false, cleanup = false) {
11586
+ if (this.retired && !cleanup) throw fsError2("ENOTCONN", this.retired);
11587
+ const stop = cleanup ? void 0 : this.stop.signal;
11588
+ const signal = init.signal && stop ? AbortSignal.any([init.signal, stop]) : init.signal ?? stop;
11577
11589
  let response;
11578
11590
  try {
11579
11591
  response = await this.fetch(`${this.grant.url}${path}`, {
11580
11592
  ...init,
11593
+ ...signal ? { signal } : {},
11581
11594
  credentials: "omit",
11582
11595
  headers: {
11583
11596
  ...init.headers,
@@ -11614,6 +11627,20 @@ var HostfsClient = class {
11614
11627
  );
11615
11628
  return await response.json();
11616
11629
  }
11630
+ async cleanup(op, body) {
11631
+ const response = await this.send(
11632
+ "/api/hostfs",
11633
+ {
11634
+ method: "POST",
11635
+ headers: { "Content-Type": "application/json" },
11636
+ body: JSON.stringify({ op, ...body })
11637
+ },
11638
+ true,
11639
+ false,
11640
+ true
11641
+ );
11642
+ await response.body?.cancel();
11643
+ }
11617
11644
  async read(fh, offset, size, ifMatch) {
11618
11645
  const response = await this.send(
11619
11646
  "/api/hostfs",
@@ -11681,6 +11708,11 @@ function hostfsHandlers(current) {
11681
11708
  return fresh(remote);
11682
11709
  };
11683
11710
  const call = (op, body = {}) => current().call(op, body);
11711
+ const settle = (client, restore) => client.call("setattr", restore).catch((err) => {
11712
+ if (!client.gone || !client.cleanup) throw err;
11713
+ void client.cleanup("setattr", restore).catch(() => {
11714
+ });
11715
+ });
11684
11716
  async function writable(client, path, flags) {
11685
11717
  const { ifMatch: _, ...asked } = flags;
11686
11718
  const opening = () => client.call("open", { path: rel(path), ...asked });
@@ -11691,9 +11723,9 @@ function hostfsHandlers(current) {
11691
11723
  const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
11692
11724
  if (mode & 128) throw err;
11693
11725
  const restore = { path: rel(path), mode };
11694
- await client.call("setattr", { path: rel(path), mode: mode | 128 });
11695
- const opened2 = await opening().catch(async (again) => {
11696
- await client.call("setattr", restore);
11726
+ const opened2 = await client.call("setattr", { path: rel(path), mode: mode | 128 }).then(opening).catch(async (again) => {
11727
+ await settle(client, restore).catch(() => {
11728
+ });
11697
11729
  throw again;
11698
11730
  });
11699
11731
  return { opened: opened2, restore };
@@ -11748,7 +11780,7 @@ function hostfsHandlers(current) {
11748
11780
  try {
11749
11781
  await fresh(remote).client.call("release", { fh: remote.fh }, true);
11750
11782
  } finally {
11751
- if (remote.restore) await current().call("setattr", remote.restore);
11783
+ if (remote.restore) await settle(current(), remote.restore);
11752
11784
  }
11753
11785
  },
11754
11786
  mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
@@ -11878,6 +11910,73 @@ function invalidation(event) {
11878
11910
  return event.paths ?? true;
11879
11911
  }
11880
11912
  var PORT_REQUEST_MS = 6e4;
11913
+ var HOSTFS_CALL_MS = 2e4;
11914
+ var PORT_BEAT_MS = 1e3;
11915
+ var PORT_DEAD_MS = 1e4;
11916
+ var PORT_LINGER_MS = 1e3;
11917
+ function timed(transport, ms) {
11918
+ const inflight = /* @__PURE__ */ new Set();
11919
+ const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
11920
+ let expired = false;
11921
+ const running = run2();
11922
+ inflight.add(running);
11923
+ const done4 = () => inflight.delete(running);
11924
+ running.then(done4, done4);
11925
+ const timer = setTimeout(() => {
11926
+ expired = true;
11927
+ const why = `the host folder did not answer ${name2} within ${ms} ms`;
11928
+ transport.retire?.(why);
11929
+ expire?.(running);
11930
+ reject(fsError2("ENOTCONN", why));
11931
+ }, ms);
11932
+ running.then(
11933
+ (value) => {
11934
+ clearTimeout(timer);
11935
+ if (expired) late?.(value);
11936
+ resolve(value);
11937
+ },
11938
+ (err) => {
11939
+ clearTimeout(timer);
11940
+ reject(err);
11941
+ }
11942
+ );
11943
+ });
11944
+ const cleanup = async (op, body, skip) => {
11945
+ await Promise.allSettled([...inflight].filter((work) => work !== skip));
11946
+ await transport.cleanup?.(op, body);
11947
+ };
11948
+ const release = (opened2) => {
11949
+ const fh = opened2?.fh;
11950
+ if (typeof fh === "number") void cleanup("release", { fh }).catch(() => {
11951
+ });
11952
+ };
11953
+ return {
11954
+ get capabilities() {
11955
+ return transport.capabilities;
11956
+ },
11957
+ get token() {
11958
+ return transport.token;
11959
+ },
11960
+ get maxIo() {
11961
+ return transport.maxIo;
11962
+ },
11963
+ get gone() {
11964
+ return transport.gone;
11965
+ },
11966
+ retire: (why) => transport.retire?.(why),
11967
+ ...transport.cleanup ? { cleanup } : {},
11968
+ call: (op, body, handle) => guard(
11969
+ op,
11970
+ () => transport.call(op, body, handle),
11971
+ op === "open" ? release : void 0,
11972
+ op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
11973
+ }) : void 0
11974
+ ),
11975
+ read: (fh, offset, size, ifMatch) => guard("read", () => transport.read(fh, offset, size, ifMatch)),
11976
+ write: (fh, offset, bytes) => guard("write", () => transport.write(fh, offset, bytes)),
11977
+ watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
11978
+ };
11979
+ }
11881
11980
  var PORT_MS_PER_MIB = 1e3;
11882
11981
  var nextPortToken = 0;
11883
11982
  var PortTransport = class {
@@ -11888,13 +11987,25 @@ var PortTransport = class {
11888
11987
  waiting = /* @__PURE__ */ new Map();
11889
11988
  next = 0;
11890
11989
  dead;
11891
- constructor(grant, requestMs = PORT_REQUEST_MS) {
11990
+ beatMs;
11991
+ deadMs;
11992
+ lingerMs;
11993
+ beat;
11994
+ answers = false;
11995
+ pinged;
11996
+ constructor(grant, liveness = {}) {
11892
11997
  this.port = grant.port;
11893
- this.requestMs = requestMs;
11998
+ this.requestMs = liveness.requestMs ?? PORT_REQUEST_MS;
11999
+ this.beatMs = liveness.beatMs ?? PORT_BEAT_MS;
12000
+ this.deadMs = liveness.deadMs ?? PORT_DEAD_MS;
12001
+ this.lingerMs = liveness.lingerMs ?? PORT_LINGER_MS;
11894
12002
  this.capabilities = grant.capabilities ?? {};
11895
12003
  this.token = grant.token ?? `port-${++nextPortToken}`;
11896
12004
  this.port.addEventListener("message", (event) => {
11897
- this.waiting.get(event.data?.id)?.(event.data);
12005
+ if (event.data?.pong) {
12006
+ this.answers = true;
12007
+ this.pinged = void 0;
12008
+ } else this.waiting.get(event.data?.id)?.(event.data);
11898
12009
  });
11899
12010
  this.port.addEventListener("close", () => this.fail("the folder's port closed"));
11900
12011
  this.port.start();
@@ -11905,8 +12016,51 @@ var PortTransport = class {
11905
12016
  get gone() {
11906
12017
  return this.dead !== void 0;
11907
12018
  }
12019
+ retire(why) {
12020
+ this.fail(why);
12021
+ }
12022
+ ping() {
12023
+ if (this.pinged !== void 0) return;
12024
+ this.pinged = Date.now();
12025
+ try {
12026
+ this.port.postMessage({ id: 0, op: "ping" });
12027
+ } catch {
12028
+ }
12029
+ }
12030
+ watchBeat() {
12031
+ if (this.beat || this.dead) return;
12032
+ this.ping();
12033
+ this.beat = setInterval(() => {
12034
+ if (this.waiting.size === 0) {
12035
+ clearInterval(this.beat);
12036
+ this.beat = void 0;
12037
+ return;
12038
+ }
12039
+ if (this.answers && this.pinged !== void 0 && Date.now() - this.pinged > this.deadMs) {
12040
+ this.fail("the folder's port stopped answering");
12041
+ return;
12042
+ }
12043
+ this.ping();
12044
+ }, this.beatMs);
12045
+ }
12046
+ async cleanup(op, body) {
12047
+ if (this.closed) throw fsError2("ENOTCONN", "the folder's port is closed");
12048
+ this.port.postMessage({ id: 0, op: "cleanup", name: op, body });
12049
+ }
12050
+ closed = false;
11908
12051
  fail(why) {
11909
- if (this.dead === void 0) this.port.close();
12052
+ if (this.dead === void 0) {
12053
+ setTimeout(() => {
12054
+ try {
12055
+ this.port.postMessage({ id: 0, op: "bye" });
12056
+ } catch {
12057
+ }
12058
+ this.closed = true;
12059
+ this.port.close();
12060
+ }, this.lingerMs);
12061
+ }
12062
+ clearInterval(this.beat);
12063
+ this.beat = void 0;
11910
12064
  this.dead ??= why;
11911
12065
  for (const settle of [...this.waiting.values()])
11912
12066
  settle({ id: 0, error: { code: "ENOTCONN", message: why } });
@@ -11923,6 +12077,7 @@ var PortTransport = class {
11923
12077
  },
11924
12078
  this.requestMs + Math.ceil(bytes / 1048576) * PORT_MS_PER_MIB
11925
12079
  );
12080
+ this.watchBeat();
11926
12081
  this.waiting.set(id2, (reply) => {
11927
12082
  clearTimeout(timer);
11928
12083
  this.waiting.delete(id2);
@@ -11969,6 +12124,7 @@ var PortTransport = class {
11969
12124
  }
11970
12125
  resolve();
11971
12126
  };
12127
+ this.watchBeat();
11972
12128
  this.waiting.set(id2, (reply) => {
11973
12129
  if (reply.open) return onOpen();
11974
12130
  if (reply.event) return onEvent(reply.event);
@@ -11992,11 +12148,38 @@ var errorOf = (err) => ({
11992
12148
  });
11993
12149
  function serveHostfsPort(port, transport) {
11994
12150
  const watches = /* @__PURE__ */ new Map();
12151
+ const handles = /* @__PURE__ */ new Set();
12152
+ let stopped = false;
12153
+ const closeHandles = () => {
12154
+ for (const fh of handles) void transport.call("release", { fh }, true).catch(() => {
12155
+ });
12156
+ handles.clear();
12157
+ };
11995
12158
  const send = (reply, transfer = []) => port.postMessage(reply, transfer);
12159
+ const pending = /* @__PURE__ */ new Set();
12160
+ const track = (work) => {
12161
+ pending.add(work);
12162
+ const done4 = () => pending.delete(work);
12163
+ work.then(done4, done4);
12164
+ return work;
12165
+ };
12166
+ const answer = async (name2, body) => {
12167
+ const result = await track(transport.call(name2, body));
12168
+ const opened2 = result?.fh;
12169
+ if (name2 === "open" && typeof opened2 === "number") handles.add(opened2);
12170
+ if (name2 === "release") handles.delete(body?.fh);
12171
+ if (stopped) closeHandles();
12172
+ return result;
12173
+ };
11996
12174
  const listener = async ({
11997
12175
  data
11998
12176
  }) => {
11999
12177
  const { id: id2, op } = data;
12178
+ if (op === "ping") return send({ id: id2, pong: true });
12179
+ if (op === "bye") return stop();
12180
+ if (op === "cleanup")
12181
+ return void Promise.allSettled([...pending]).then(() => answer(data.name, data.body)).catch(() => {
12182
+ });
12000
12183
  if (op === "unwatch") return watches.get(id2)?.abort();
12001
12184
  if (op === "watch") {
12002
12185
  const stop2 = new AbortController();
@@ -12017,32 +12200,35 @@ function serveHostfsPort(port, transport) {
12017
12200
  }
12018
12201
  try {
12019
12202
  if (op === "read") {
12020
- const read = await transport.read(
12021
- data.fh,
12022
- data.offset,
12023
- data.size,
12024
- data.ifMatch
12203
+ const read = await track(
12204
+ transport.read(
12205
+ data.fh,
12206
+ data.offset,
12207
+ data.size,
12208
+ data.ifMatch
12209
+ )
12025
12210
  );
12026
12211
  const bytes = read.slice();
12027
12212
  return send({ id: id2, bytes }, [bytes.buffer]);
12028
12213
  }
12029
12214
  if (op === "write") {
12030
- await transport.write(data.fh, data.offset, data.bytes);
12215
+ await track(
12216
+ transport.write(data.fh, data.offset, data.bytes)
12217
+ );
12031
12218
  return send({ id: id2 });
12032
12219
  }
12033
12220
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
12034
- send({
12035
- id: id2,
12036
- result: await transport.call(data.name, data.body)
12037
- });
12221
+ send({ id: id2, result: await answer(data.name, data.body) });
12038
12222
  } catch (err) {
12039
12223
  send({ id: id2, error: errorOf(err) });
12040
12224
  }
12041
12225
  };
12042
12226
  const stop = () => {
12227
+ stopped = true;
12043
12228
  port.removeEventListener("message", listener);
12044
12229
  port.removeEventListener("close", stop);
12045
12230
  for (const watch of watches.values()) watch.abort();
12231
+ closeHandles();
12046
12232
  port.close();
12047
12233
  };
12048
12234
  port.addEventListener("message", listener);
@@ -12050,10 +12236,10 @@ function serveHostfsPort(port, transport) {
12050
12236
  port.start();
12051
12237
  return stop;
12052
12238
  }
12053
- function grantConnect(hook, fetch2, requestMs) {
12239
+ function grantConnect(hook, fetch2, liveness = {}) {
12054
12240
  return async (source, options) => {
12055
12241
  const first = await hook(source, options);
12056
- if ("port" in first) return new PortTransport(first, requestMs);
12242
+ if ("port" in first) return new PortTransport(first, liveness);
12057
12243
  const grant = async () => {
12058
12244
  const again = await hook(source, options);
12059
12245
  if ("port" in again)
@@ -12073,7 +12259,10 @@ async function openHostfs(spec, connect, timing = {}) {
12073
12259
  const stop = new AbortController();
12074
12260
  let active;
12075
12261
  const handlers = hostfsHandlers(() => active);
12076
- const ranged2 = () => active?.capabilities.ranges === true ? { ranges: true } : {};
12262
+ const ranged2 = () => ({
12263
+ ...active?.capabilities.ranges === true ? { ranges: true } : {},
12264
+ ...active ? { maxIo: Math.min(active.maxIo, HOSTFS_IO) } : {}
12265
+ });
12077
12266
  const served = serveFilesystem(
12078
12267
  port2,
12079
12268
  { ...slot.handlers, mount: ranged2 },
@@ -12089,7 +12278,7 @@ async function openHostfs(spec, connect, timing = {}) {
12089
12278
  {
12090
12279
  connect: () => connect(spec.source, { readonly: readonly2 }),
12091
12280
  up(client) {
12092
- active = client;
12281
+ active = timed(client, timing.callMs ?? HOSTFS_CALL_MS);
12093
12282
  slot.insert(client, handlers);
12094
12283
  served.invalidate(true);
12095
12284
  },
@@ -13118,11 +13307,7 @@ var Launcher = class {
13118
13307
  throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
13119
13308
  return openHostfs(
13120
13309
  spec,
13121
- grantConnect(
13122
- hostfs,
13123
- hostfsFetch ?? ((url, init) => fetch(url, init)),
13124
- hostfsTiming?.requestMs
13125
- ),
13310
+ grantConnect(hostfs, hostfsFetch ?? ((url, init) => fetch(url, init)), hostfsTiming),
13126
13311
  hostfsTiming
13127
13312
  );
13128
13313
  }