@ai-ecoverse/slicc-kernel 1.102.3 → 1.102.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -389,7 +389,7 @@ const kernel = await createKernel({ network: { transport: fetchTransport() } });
389
389
  await kernel.run(['curl', '-sS', 'https://registry.npmjs.org/@ai-ecoverse/wasm-bash/latest']);
390
390
  ```
391
391
 
392
- - **Sockets**: `AF_INET` stream sockets on `127.x` and `AF_UNIX` sockets are kernel descriptors on one loopback network per kernel, so processes can serve and connect to each other. They survive `dup`, `fork` and `exec` and work with `select`, `poll` and WASIX epoll, like pipe ends. A bind of an `AF_UNIX` socket creates its path in OPFS. Other addresses are unreachable (`ENETUNREACH`) unless the [uplink](#uplink) routes them: otherwise programs reach the outside world only through the proxy.
392
+ - **Sockets**: `AF_INET` stream sockets on `127.x` and `AF_UNIX` sockets are kernel descriptors on one loopback network per kernel, so processes can serve and connect to each other. They survive `dup`, `fork` and `exec` and work with `select`, `poll` and WASIX epoll, like pipe ends. A bind of an `AF_UNIX` socket creates its path in OPFS. A program's `connect` to a listener whose accept queue is full waits for a slot, as on Linux, instead of failing. A blocking connect waits, and a nonblocking one answers `EINPROGRESS` and turns writable once it's queued. After 30 s it fails with `ETIMEDOUT`, and it's `ECONNREFUSED` if the listener closes. Other addresses are unreachable (`ENETUNREACH`) unless the [uplink](#uplink) routes them: otherwise programs reach the outside world only through the proxy.
393
393
 
394
394
  #### Datagram sockets (UDP)
395
395
 
@@ -3564,6 +3564,7 @@ var KernelSocket = class _KernelSocket {
3564
3564
  backlog = 0;
3565
3565
  boundAt;
3566
3566
  changes = new Changes();
3567
+ room = new Changes();
3567
3568
  options = /* @__PURE__ */ new Map();
3568
3569
  constructor(net, domain) {
3569
3570
  this.net = net;
@@ -3648,7 +3649,10 @@ var KernelSocket = class _KernelSocket {
3648
3649
  for (; ; ) {
3649
3650
  if (this.state !== "listening") throw new KernelError("EINVAL");
3650
3651
  const next2 = this.queue.shift();
3651
- if (next2) return next2;
3652
+ if (next2) {
3653
+ this.room.wake();
3654
+ return next2;
3655
+ }
3652
3656
  if (nonblock) throw new KernelError("EAGAIN");
3653
3657
  await this.changes.wait(signal);
3654
3658
  }
@@ -3659,7 +3663,13 @@ var KernelSocket = class _KernelSocket {
3659
3663
  this.changes.wake();
3660
3664
  return true;
3661
3665
  }
3662
- connect(addr, viaUplink = true, cred, stamp) {
3666
+ get full() {
3667
+ return this.state === "listening" && this.queue.length >= this.backlog;
3668
+ }
3669
+ waitRoom(signal) {
3670
+ return this.room.wait(signal);
3671
+ }
3672
+ connect(addr, viaUplink = true, cred, stamp, waits = true) {
3663
3673
  if (this.state === "connected") throw new KernelError("EISCONN");
3664
3674
  if (this.state === "connecting") throw new KernelError("EALREADY");
3665
3675
  if (this.state !== "open") throw new KernelError("EINVAL");
@@ -3674,6 +3684,11 @@ var KernelSocket = class _KernelSocket {
3674
3684
  this.boundAt = this.local;
3675
3685
  } else this.local = { family: "unix", path: "" };
3676
3686
  }
3687
+ if (waits && target.full) return this.queued(addr, target, cred, stamp);
3688
+ this.join(addr, target, cred, stamp);
3689
+ return void 0;
3690
+ }
3691
+ join(addr, target, cred, stamp) {
3677
3692
  const server = new _KernelSocket(this.net, this.domain);
3678
3693
  server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
3679
3694
  server.peer = this.local;
@@ -3687,7 +3702,34 @@ var KernelSocket = class _KernelSocket {
3687
3702
  server.close();
3688
3703
  throw new KernelError("ECONNREFUSED");
3689
3704
  }
3690
- return void 0;
3705
+ }
3706
+ async queued(addr, target, cred, stamp) {
3707
+ this.state = "connecting";
3708
+ const abort = new AbortController();
3709
+ this.dialing = abort;
3710
+ const timer = setTimeout(
3711
+ () => abort.abort(new KernelError("ETIMEDOUT")),
3712
+ this.net.backlogWaitMs
3713
+ );
3714
+ try {
3715
+ while (target.full) await target.waitRoom(abort.signal);
3716
+ if (abort.signal.aborted) throw abort.signal.reason;
3717
+ this.state = "open";
3718
+ this.join(addr, target, cred, stamp);
3719
+ this.dialing = void 0;
3720
+ } catch (e) {
3721
+ const reason = abort.signal.aborted ? abort.signal.reason : e;
3722
+ const error = reason instanceof KernelError ? reason : new KernelError("ECONNREFUSED");
3723
+ if (this.dialing === abort) {
3724
+ this.dialing = void 0;
3725
+ this.failed = error.code;
3726
+ if (this.state === "connecting") this.state = "open";
3727
+ this.changes.wake();
3728
+ }
3729
+ throw error;
3730
+ } finally {
3731
+ clearTimeout(timer);
3732
+ }
3691
3733
  }
3692
3734
  dial(addr, route) {
3693
3735
  this.state = "connecting";
@@ -3788,6 +3830,7 @@ var KernelSocket = class _KernelSocket {
3788
3830
  this.dialing = void 0;
3789
3831
  if (was === "listening") {
3790
3832
  for (const pending of this.queue.splice(0)) pending.close();
3833
+ this.room.wake();
3791
3834
  }
3792
3835
  if (this.link) {
3793
3836
  if (!this.readShut) this.link.rx.closeRead();
@@ -3885,9 +3928,10 @@ var LoopbackNet = class {
3885
3928
  socket.listen(backlog);
3886
3929
  return socket;
3887
3930
  }
3931
+ backlogWaitMs = DIAL_TIMEOUT_MS;
3888
3932
  connect(addr) {
3889
3933
  const socket = this.socket(addr.family);
3890
- void socket.connect(addr, false);
3934
+ void socket.connect(addr, false, void 0, void 0, false);
3891
3935
  return socket;
3892
3936
  }
3893
3937
  bind(socket, addr, kernel = false) {
@@ -8273,7 +8317,8 @@ var GEN = 0;
8273
8317
  var RING = 1;
8274
8318
  var RING_SIZE = 4096;
8275
8319
  var SLOT = 2;
8276
- var CHANGES_BYTES = (RING + RING_SIZE * SLOT) * 4;
8320
+ var DIRTY = RING + RING_SIZE * SLOT;
8321
+ var CHANGES_BYTES = (DIRTY + 1) * 4;
8277
8322
  function changeHash(path) {
8278
8323
  let h = 2166136261;
8279
8324
  for (let i = 0; i < path.length; i++) {
@@ -8298,6 +8343,9 @@ var FileChanges = class {
8298
8343
  this.sab = sab;
8299
8344
  this.view = new Int32Array(sab);
8300
8345
  }
8346
+ dirtied(delta) {
8347
+ Atomics.add(this.view, DIRTY, delta);
8348
+ }
8301
8349
  publish(...paths) {
8302
8350
  for (const path of paths) {
8303
8351
  const g = Atomics.add(this.view, GEN, 1) + 1 | 0;
@@ -12136,7 +12184,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
12136
12184
  var EARLY_WHOLE_FILE = 64 * 1024;
12137
12185
 
12138
12186
  // src/version.ts
12139
- var KERNEL_VERSION = true ? "1.102.3" : "0.0.0-dev";
12187
+ var KERNEL_VERSION = true ? "1.102.5" : "0.0.0-dev";
12140
12188
  var OS_RELEASE = "6.0.0-slicc";
12141
12189
  function procVersion() {
12142
12190
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -13391,7 +13439,8 @@ var KernelLoopback = class {
13391
13439
  { family: "inet", host: "127.0.0.1", port },
13392
13440
  false,
13393
13441
  this.cred(),
13394
- this.stamp ? await this.stamp() : void 0
13442
+ this.stamp ? await this.stamp() : void 0,
13443
+ false
13395
13444
  );
13396
13445
  } catch (err) {
13397
13446
  socket.close();
@@ -15017,6 +15066,15 @@ var VfsNode = class {
15017
15066
  orphaned;
15018
15067
  id;
15019
15068
  onDirty;
15069
+ onDirtyState;
15070
+ counted = false;
15071
+ recount() {
15072
+ if (!this.onDirtyState) return;
15073
+ const on = this.dirty && !this.orphaned;
15074
+ if (on === this.counted) return;
15075
+ this.counted = on;
15076
+ this.onDirtyState?.(on);
15077
+ }
15020
15078
  constructor(fs, path, contents2, orphaned = false, dirty = false, pin) {
15021
15079
  this.fs = fs;
15022
15080
  this.pin = pin;
@@ -15166,6 +15224,7 @@ var VfsNode = class {
15166
15224
  }
15167
15225
  markDirty() {
15168
15226
  this.dirty = true;
15227
+ this.recount();
15169
15228
  if (!this.orphaned) this.onDirty?.();
15170
15229
  if (this.writeBack !== void 0 || this.orphaned) return;
15171
15230
  const delay = Math.max(WRITEBACK_MS, this.writeBackCost * WRITEBACK_COST_FACTOR);
@@ -15193,6 +15252,8 @@ var VfsNode = class {
15193
15252
  } catch (err) {
15194
15253
  this.dirty = true;
15195
15254
  throw err;
15255
+ } finally {
15256
+ this.recount();
15196
15257
  }
15197
15258
  this.writeBackCost = performance.now() - started;
15198
15259
  }
@@ -15203,6 +15264,7 @@ var VfsNodes = class {
15203
15264
  byId = /* @__PURE__ */ new Map();
15204
15265
  keepsOnDisk = false;
15205
15266
  changed;
15267
+ dirtied;
15206
15268
  fs;
15207
15269
  entries;
15208
15270
  onClosed;
@@ -15286,6 +15348,7 @@ var VfsNodes = class {
15286
15348
  }
15287
15349
  orphan(node) {
15288
15350
  node.orphaned = true;
15351
+ node.recount();
15289
15352
  this.forget(node);
15290
15353
  }
15291
15354
  dropName(path, node) {
@@ -15340,6 +15403,8 @@ var VfsNodes = class {
15340
15403
  create(path, id2, node) {
15341
15404
  this.reshaped();
15342
15405
  node.onDirty = () => this.changed?.(node.path);
15406
+ node.onDirtyState = (on) => this.dirtied?.(on ? 1 : -1);
15407
+ node.recount();
15343
15408
  this.byPath.set(path, node);
15344
15409
  if (id2 !== void 0) this.know(node, id2);
15345
15410
  return node;
@@ -22556,6 +22621,7 @@ var Launcher = class {
22556
22621
  }
22557
22622
  processChain(fs, nodes, unlinked) {
22558
22623
  nodes.changed = (path) => this.changes.publish(path);
22624
+ nodes.dirtied = (delta) => this.changes.dirtied(delta);
22559
22625
  return unlinked.wrap(keepingOpen(fs, nodes, (path) => this.changes.publish(path)));
22560
22626
  }
22561
22627
  async starting(argv, dir, account, tag) {