@ai-ecoverse/slicc-kernel 1.142.2 → 1.142.3

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
@@ -202,10 +202,12 @@ const { status, stdout } = await kernel.run(['bash', '-c', 'echo hi'], { cwd: '/
202
202
  kernel.terminate();
203
203
  ```
204
204
 
205
- `createNodeKernel({ root, modules, commands, env, network, worker, facades, processMounts, fstabRetries, cdp, hostfs, hostfsOrigin, hostfsFetch, users, onDenial, approve, compileCache })` takes the options of `createKernel` except `metadata`, and `fstabRetries`, the delays in ms between tries of a failing `/etc/fstab` line (default `[1000, 4000, 16000]`). `root` is an in-memory directory by default (`memoryRoot()` makes another), which keeps each file in 1 MiB chunks, and POSIX metadata stays in memory. Processes and threads run on `worker_threads`. The kernel has `run`, `openTerminal`, `users`, `setRoutes`, `updateTransportTraits` and `terminate` as above, plus `integrity.revoke(path?)`, `root`, `writeFile(path, data, { mode }?)` (creating the parent directories; `mode` sets the permission bits, `0` to `0o7777`, before the data is written), `chmod(path, mode)` and `readFile(path)` to put files in place and read results; like the `fs` of an attached client, they go through the mounts, as the processes see them. `nodeTransport()` is `fetchTransport()` with Node's `fetch`, which no CORS binds (`crossOrigin: 'any'`). `connect({ user })` and `attachKernel` work as in the browser: the port is a `worker_threads` `MessagePort`, which a worker thread can attach with, and `terminate()` ends every attached client.
205
+ `createNodeKernel({ root, modules, commands, env, network, worker, facades, processMounts, fstabRetries, cdp, hostfs, hostfsOrigin, hostfsFetch, users, onDenial, approve, compileCache, warmWorker })` takes the options of `createKernel` except `metadata`, and `fstabRetries`, the delays in ms between tries of a failing `/etc/fstab` line (default `[1000, 4000, 16000]`). `root` is an in-memory directory by default (`memoryRoot()` makes another), which keeps each file in 1 MiB chunks, and POSIX metadata stays in memory. Processes and threads run on `worker_threads`. The kernel has `run`, `openTerminal`, `users`, `setRoutes`, `updateTransportTraits` and `terminate` as above, plus `integrity.revoke(path?)`, `root`, `writeFile(path, data, { mode }?)` (creating the parent directories; `mode` sets the permission bits, `0` to `0o7777`, before the data is written), `chmod(path, mode)` and `readFile(path)` to put files in place and read results; like the `fs` of an attached client, they go through the mounts, as the processes see them. `nodeTransport()` is `fetchTransport()` with Node's `fetch`, which no CORS binds (`crossOrigin: 'any'`). `connect({ user })` and `attachKernel` work as in the browser: the port is a `worker_threads` `MessagePort`, which a worker thread can attach with, and `terminate()` ends every attached client.
206
206
 
207
207
  `compileCache` (opt-in, off by default; #1285, slicc-jsh#566) is an absolute host directory where each process worker keeps the compiled code of the JS programs it imports (`module.enableCompileCache`), so the next spawn of the same program skips V8's compile. It stores V8's code cache, keyed and checksummed by the module's source, one file per program (about 7 MB for a 2.4 MB bundle), shared by every kernel given the same directory; programs can't reach or write it (the worker hands it to Node before the lockdown, and `node:worker_threads` stays refused). Measured on a 2.4 MB bundle, a spawn went from about 54 to 39 ms; a trivial program costs about 1 ms more. **The directory must be private to the embedder:** V8's code cache is trusted bytecode that Node only checksums, so whoever can write it can plant code that runs inside a process worker past the lockdown. Never make it reachable from inside the kernel (no `hostfs` mount of it or of a parent), and don't share one between kernels of different trust. A host's own `NODE_COMPILE_CACHE` is never handed to the process workers, with or without the option, so programs never land in a host-wide cache. If a host preload (`--import` or `--require`) has already enabled another compile cache in the workers, a kernel given `compileCache` refuses to run programs there instead of caching them in the host's directory.
208
208
 
209
+ `warmWorker` (on by default; `false` turns it off; #1341, slicc-jsh#566) keeps one process worker booted ahead of the next spawn, fork or thread on the Node entry, so the spawn skips the worker's boot: it has loaded the process worker module and nothing else, no pid, credentials, environment, cwd, descriptors, lockdown or program, all of which arrive with its init message, and every worker still serves exactly one process or thread and is then terminated. Its own `process.env` is a snapshot taken when the spare is created rather than at the spawn, but a program never sees it (`process` is withheld, and a program's environment is the one its spawn gives it). The spare never keeps the host alive, is replaced after each spawn off the critical path, is dropped if it errors or exits while idle, and is released after 30 s without a spawn; `terminate()` ends it. It costs about 11 MB of host RSS while it waits.
210
+
209
211
  By default `nodeTransport()` refuses destinations on the host itself, because Node can reach them and a page can't: local DevTools on 9222–9229, dev servers, local admin APIs, and the cloud metadata address.
210
212
  - **What it refuses:** loopback (`127.0.0.0/8`, `::1`, and their IPv4-mapped forms), unspecified addresses (`0.0.0.0/8`, `::`), `localhost` and `*.localhost`, and link-local addresses (`169.254.0.0/16`, `fe80::/10`), plus AWS's IPv6 metadata address `fd00:ec2::254`.
211
213
  - **How it checks:** it matches on the addresses the name resolves to, so a public name that resolves to `127.0.0.1` is refused too. It checks every request and WebSocket open, `host.slicc.internal` and each redirect hop included.
@@ -7595,7 +7595,7 @@ function satisfies(version, range2) {
7595
7595
  }
7596
7596
 
7597
7597
  // src/version.ts
7598
- var KERNEL_VERSION = true ? "1.142.2" : "0.0.0-dev";
7598
+ var KERNEL_VERSION = true ? "1.142.3" : "0.0.0-dev";
7599
7599
  var OS_RELEASE = "6.0.0-slicc";
7600
7600
  function procVersion() {
7601
7601
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -6725,7 +6725,7 @@ var KERNEL_VERSION, OS_RELEASE;
6725
6725
  var init_version = __esm({
6726
6726
  "src/version.ts"() {
6727
6727
  "use strict";
6728
- KERNEL_VERSION = true ? "1.142.2" : "0.0.0-dev";
6728
+ KERNEL_VERSION = true ? "1.142.3" : "0.0.0-dev";
6729
6729
  OS_RELEASE = "6.0.0-slicc";
6730
6730
  }
6731
6731
  });
package/dist/node.js CHANGED
@@ -7979,7 +7979,7 @@ function satisfies(version, range2) {
7979
7979
  }
7980
7980
 
7981
7981
  // src/version.ts
7982
- var KERNEL_VERSION = true ? "1.142.2" : "0.0.0-dev";
7982
+ var KERNEL_VERSION = true ? "1.142.3" : "0.0.0-dev";
7983
7983
  var OS_RELEASE = "6.0.0-slicc";
7984
7984
  function procVersion() {
7985
7985
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -32110,7 +32110,66 @@ function nodeTransport(options = {}) {
32110
32110
  }
32111
32111
  var TERMINATED = "the kernel is terminated";
32112
32112
  var EXIT_GRACE_MS = 50;
32113
- function nodeWorker(file, live, closed, workerData) {
32113
+ var WARM_IDLE_MS = 3e4;
32114
+ function warmPool(make, live, idleMs = WARM_IDLE_MS) {
32115
+ let spare;
32116
+ let idle2;
32117
+ let closed = false;
32118
+ const drop = (worker) => {
32119
+ if (spare !== worker) return;
32120
+ spare = void 0;
32121
+ clearTimeout(idle2);
32122
+ live.delete(kill);
32123
+ };
32124
+ const kill = () => {
32125
+ const worker = spare;
32126
+ if (!worker) return;
32127
+ drop(worker);
32128
+ void worker.terminate();
32129
+ };
32130
+ const fill2 = () => {
32131
+ if (closed || spare) return;
32132
+ let worker;
32133
+ try {
32134
+ worker = make();
32135
+ } catch {
32136
+ closed = true;
32137
+ return;
32138
+ }
32139
+ worker.unref();
32140
+ worker.once("error", () => drop(worker));
32141
+ worker.once("exit", () => drop(worker));
32142
+ spare = worker;
32143
+ live.add(kill);
32144
+ idle2 = setTimeout(kill, idleMs);
32145
+ idle2.unref?.();
32146
+ };
32147
+ return {
32148
+ take() {
32149
+ const worker = spare;
32150
+ if (worker) {
32151
+ drop(worker);
32152
+ worker.removeAllListeners("error");
32153
+ worker.removeAllListeners("exit");
32154
+ worker.ref();
32155
+ }
32156
+ setImmediate(fill2);
32157
+ return worker ?? make();
32158
+ },
32159
+ close() {
32160
+ closed = true;
32161
+ kill();
32162
+ }
32163
+ };
32164
+ }
32165
+ function processWorker(file, workerData) {
32166
+ return new Worker(file, { env: workerEnv(), ...workerData ? { workerData } : {} });
32167
+ }
32168
+ function warmPoolOf(options, file, workerData, live) {
32169
+ if (options.warmWorker === false) return void 0;
32170
+ return warmPool(() => processWorker(file, workerData), live);
32171
+ }
32172
+ function nodeWorker(file, live, closed, workerData, take) {
32114
32173
  const errors = /* @__PURE__ */ new Set();
32115
32174
  const messages = /* @__PURE__ */ new Map();
32116
32175
  const fail3 = (message3) => {
@@ -32119,7 +32178,7 @@ function nodeWorker(file, live, closed, workerData) {
32119
32178
  } });
32120
32179
  }
32121
32180
  };
32122
- const worker = closed ? void 0 : new Worker(file, { env: workerEnv(), ...workerData ? { workerData } : {} });
32181
+ const worker = closed ? void 0 : take?.() ?? processWorker(file, workerData);
32123
32182
  let ending = false;
32124
32183
  if (worker) {
32125
32184
  const kill = () => {
@@ -32192,11 +32251,13 @@ async function createNodeKernel(options = {}) {
32192
32251
  let terminated = false;
32193
32252
  const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
32194
32253
  const compileCache = cacheDirOf(options.compileCache);
32254
+ const workerData = compileCache ? { compileCache } : void 0;
32255
+ const warm = warmPoolOf(options, file, workerData, live);
32195
32256
  const transport = options.network?.transport;
32196
32257
  const launcher = new Launcher({
32197
32258
  fs,
32198
32259
  unlinkOwner: pidOwner(process),
32199
- createWorker: () => nodeWorker(file, live, terminated, compileCache ? { compileCache } : void 0),
32260
+ createWorker: () => nodeWorker(file, live, terminated, workerData, warm ? () => warm.take() : void 0),
32200
32261
  createDriverWorker: () => nodeWorker(
32201
32262
  options.driverWorker ?? new URL("./node-driver-worker.js", import.meta.url),
32202
32263
  live,
@@ -32329,6 +32390,7 @@ async function createNodeKernel(options = {}) {
32329
32390
  },
32330
32391
  terminate() {
32331
32392
  terminated = true;
32393
+ warm?.close();
32332
32394
  for (const client of [...clients]) client.detach();
32333
32395
  for (const reject of [...pending]) reject(new Error(TERMINATED));
32334
32396
  for (const kill of [...live]) kill();
@@ -32340,11 +32402,13 @@ async function createNodeKernel(options = {}) {
32340
32402
  export {
32341
32403
  KernelCallError,
32342
32404
  KernelGoneError,
32405
+ WARM_IDLE_MS,
32343
32406
  attachKernel,
32344
32407
  createNodeKernel,
32345
32408
  fetchTransport,
32346
32409
  memoryRoot,
32347
32410
  nodeTransport,
32348
- serveHostfsPort
32411
+ serveHostfsPort,
32412
+ warmPool
32349
32413
  };
32350
32414
  //# sourceMappingURL=node.js.map