kern-sandbox 0.1.12 → 0.1.13

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.js +37 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **[kern](https://github.com/getkern/kern)** is a fast, rootless sandbox and virtual resource
4
4
  runtime for any workload, including untrusted and AI-generated code: a real, kernel-enforced box
5
- that starts in **3.6 ms** from an OCI image, out of one **~1.8 MB** binary, with no daemon.
5
+ that starts in **3.4 ms** from an OCI image, out of one **~1.8 MB** binary, with no daemon.
6
6
  **kern-sandbox**
7
7
  is its Node / TypeScript binding: run untrusted or agent-generated code in a fresh, isolated box, from Node.
8
8
 
@@ -177,7 +177,7 @@ can still WRITE an artifact to the workspace and `readFile` it if you prefer.
177
177
  **Warm kernel (kill the interpreter boot).** Each `runCode` starts a **fresh** interpreter, paying the
178
178
  CPython boot (~12 ms) every call. When you run many cells that share state (a REPL, a notebook, an
179
179
  agent's tool loop), open a `kernel()`: ONE warm interpreter in a long-lived box, fed cells over a pipe.
180
- In-memory state persists across cells and the per-cell cost drops from ~16 ms to **sub-millisecond**
180
+ In-memory state persists across cells and the per-cell cost drops from ~14 ms to **sub-millisecond**
181
181
  (~300x). Same rich `results` capture as `runCode`.
182
182
 
183
183
  ```js
package/index.js CHANGED
@@ -36,7 +36,7 @@ const crypto = require("crypto");
36
36
  const zlib = require("zlib");
37
37
  const { spawn, spawnSync } = require("child_process");
38
38
 
39
- const VERSION = "0.1.12";
39
+ const VERSION = "0.1.13";
40
40
 
41
41
  const DEFAULT_IMAGE = "python:3.12-slim";
42
42
  const WORKSPACE = "/workspace"; // where the persistent workspace is mounted inside every box
@@ -968,19 +968,16 @@ class Sandbox {
968
968
  const err = cappedCollector(child.stderr, this.maxOutputBytes, cbErr);
969
969
  let timedOut = false;
970
970
  let settled = false;
971
-
972
- const timer = setTimeout(() => {
973
- timedOut = true;
974
- this._teardown(child, name, childEnv);
975
- }, timeoutS * 1000);
976
-
977
- // Hard safety net: a CPU-bound box can survive our signals until kern's backstop reaps it; never
978
- // hang the caller. If close hasn't fired a few seconds after our teardown, resolve anyway.
971
+ // Both timers are armed further down, once `finish` exists. They are declared here, as `let`,
972
+ // so the closures that clear them can never reference a binding in its temporal dead zone
973
+ // whatever the callback ordering turns out to be.
974
+ let timer = null;
979
975
  let hardTimer = null;
976
+
980
977
  const finish = (code, signal) => {
981
978
  if (settled) return;
982
979
  settled = true;
983
- clearTimeout(timer);
980
+ if (timer) clearTimeout(timer);
984
981
  if (hardTimer) clearTimeout(hardTimer);
985
982
  // kern has read the file by the time it exits; leaving it behind would accrete one per call
986
983
  // in a persistent `workspace`.
@@ -1008,20 +1005,22 @@ class Sandbox {
1008
1005
  child.on("close", (code, signal) => {
1009
1006
  finish(code, signal);
1010
1007
  });
1011
- // arm the hard net only once we've decided to kill (teardown sets timedOut)
1008
+ // Hard safety net: a CPU-bound box can survive our signals until kern's backstop reaps it;
1009
+ // never hang the caller. If close hasn't fired a few seconds after our teardown, resolve anyway.
1012
1010
  const armHardNet = () => {
1013
1011
  if (hardTimer) return;
1014
1012
  hardTimer = setTimeout(() => finish(EXIT_SIGKILL, "SIGKILL"), 10000);
1015
1013
  };
1016
- // re-check shortly after the deadline in case teardown fired
1017
- const watch = setInterval(() => {
1018
- if (settled) {
1019
- clearInterval(watch);
1020
- } else if (timedOut) {
1021
- clearInterval(watch);
1022
- armHardNet();
1023
- }
1024
- }, 250);
1014
+
1015
+ timer = setTimeout(() => {
1016
+ timedOut = true;
1017
+ this._teardown(child, name, childEnv);
1018
+ // Armed HERE, at the one place that decides to kill. It used to be noticed instead by a
1019
+ // 250 ms setInterval that `finish` never cleared, so after every call that interval kept the
1020
+ // event loop alive until its own next tick: measured 224 to 232 ms of dead time between a
1021
+ // call resolving and the process being able to exit, against 19 to 27 ms of real work.
1022
+ armHardNet();
1023
+ }, timeoutS * 1000);
1025
1024
  });
1026
1025
  }
1027
1026
 
@@ -1726,7 +1725,24 @@ class Kernel {
1726
1725
  } catch {
1727
1726
  /* ignore */
1728
1727
  }
1729
- await new Promise((r) => setTimeout(r, 150));
1728
+ // Wait for the exit EVENT, capped at 150 ms, rather than sleeping 150 ms unconditionally: that
1729
+ // fixed sleep cost 152 ms on every close of a persistent kernel (measured) for a box that
1730
+ // exits in a few. A child that is already gone has emitted `exit` and will not emit it again,
1731
+ // so that case is tested directly instead of waited on.
1732
+ if (child.exitCode === null && child.signalCode === null) {
1733
+ await new Promise((resolve) => {
1734
+ let t = null;
1735
+ const onExit = () => {
1736
+ if (t !== null) clearTimeout(t);
1737
+ resolve();
1738
+ };
1739
+ t = setTimeout(() => {
1740
+ child.removeListener("exit", onExit);
1741
+ resolve();
1742
+ }, 150);
1743
+ child.once("exit", onExit);
1744
+ });
1745
+ }
1730
1746
  this._kill();
1731
1747
  } else {
1732
1748
  this._kill();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-sandbox",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "kern is a fast, rootless sandbox and virtual resource runtime for any workload, including untrusted and AI-generated code; kern-sandbox is its Node/TypeScript binding. Run untrusted or agent-generated code (Python/JS/Bash) in a real, kernel-enforced box in single-digit milliseconds, with no cloud, no account and no VM.",
5
5
  "keywords": [
6
6
  "sandbox",