sandboxedjs 0.2.12 → 0.2.14
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 +5 -0
- package/dist/index.cjs +87 -3
- package/dist/index.js +87 -3
- package/dist/python/python.data +140 -141
- package/dist/python/python.js +1 -1
- package/dist/python/python.wasm +0 -0
- package/dist/python/runtime.json +3 -3
- package/dist/python-worker.js +9 -0
- package/docs/browser-runtime-architecture.md +37 -41
- package/docs/python/release-gates.md +1 -1
- package/package.json +2 -1
package/dist/python/python.wasm
CHANGED
|
Binary file
|
package/dist/python/runtime.json
CHANGED
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"artifacts": {
|
|
19
19
|
"moduleUrl": "./python.js",
|
|
20
20
|
"hashes": {
|
|
21
|
-
"python.js": "sha256-
|
|
22
|
-
"python.wasm": "sha256-
|
|
23
|
-
"python.data": "sha256-
|
|
21
|
+
"python.js": "sha256-2077b7c944097fa32fd80ffdd2575d5877a75bea77a0083f3dce93e7d2ab2976",
|
|
22
|
+
"python.wasm": "sha256-03f5cb9889a26d5f0b5a3e9d7310e2c5d9f2e8d1c4f9673b061ee19fcbe759c1",
|
|
23
|
+
"python.data": "sha256-c6e6bcf4afd43d12b50c6a0d013d400868434bbfd37341d4642ea40ad69cacd3",
|
|
24
24
|
"python.worker.js": "sha256-63bb1df52c4e3a95eae7b2d77c98554188fcc5925e72628ea0c34d5c573977a5"
|
|
25
25
|
}
|
|
26
26
|
},
|
package/dist/python-worker.js
CHANGED
|
@@ -802,6 +802,7 @@ async function runPythonProcess(start, port) {
|
|
|
802
802
|
)).default;
|
|
803
803
|
let exitCode = 0;
|
|
804
804
|
let guestMemory;
|
|
805
|
+
let threadBridgeName;
|
|
805
806
|
const guestHeap = () => {
|
|
806
807
|
const exported = config.HEAPU8;
|
|
807
808
|
if (exported && exported.byteLength > 0) return exported;
|
|
@@ -886,6 +887,7 @@ async function runPythonProcess(start, port) {
|
|
|
886
887
|
if (!guestMemory && result.instance.exports.memory instanceof WebAssembly.Memory) {
|
|
887
888
|
guestMemory = result.instance.exports.memory;
|
|
888
889
|
}
|
|
890
|
+
threadBridgeName = result.instance.exports.sbx_thread_bridge_name;
|
|
889
891
|
receiveInstance(result.instance, result.module);
|
|
890
892
|
} catch (error) {
|
|
891
893
|
config.onAbort?.(error);
|
|
@@ -903,6 +905,13 @@ async function runPythonProcess(start, port) {
|
|
|
903
905
|
mountContainer(config.FS, client, start.mounts, config.ERRNO_CODES ?? {});
|
|
904
906
|
bindStdio(config.FS, client, start.isTty);
|
|
905
907
|
for (const [key, value] of Object.entries(start.env)) config.ENV[key] = value;
|
|
908
|
+
if (start.threadChannel && typeof threadBridgeName === "function") {
|
|
909
|
+
const bytes = new TextEncoder().encode(start.threadChannel + "\0");
|
|
910
|
+
if (bytes.length > 128) throw new Error("Python thread channel name is too long");
|
|
911
|
+
guestHeap().set(bytes, threadBridgeName());
|
|
912
|
+
port.postMessage({ type: "thread-memory", memory: guestMemory });
|
|
913
|
+
config.ENV.SBX_THREAD_HOST_CALLS = "1";
|
|
914
|
+
}
|
|
906
915
|
try {
|
|
907
916
|
config.FS.chdir(start.cwd);
|
|
908
917
|
} catch {
|
|
@@ -99,44 +99,40 @@ carry a TLS handshake this transport cannot terminate. A page's `fetch` is also
|
|
|
99
99
|
subject to CORS, so a cross-origin target must send the headers that let the
|
|
100
100
|
page read the response; that is a browser rule, not a container policy.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
JavaScript filesystem the image is built on, since that filesystem lives on
|
|
140
|
-
one thread by construction.
|
|
141
|
-
|
|
142
|
-
Both are runtime-image work measured in days, not a patch plus a rebuild.
|
|
102
|
+
The bundled startup hook supplies an HTTPX transport when the package is
|
|
103
|
+
installed, so `httpx.get()` and `httpx.AsyncClient` use the same egress endpoint
|
|
104
|
+
in a browser. This is an integration for HTTPX rather than a claim that every
|
|
105
|
+
third-party socket or TLS library is browser-compatible; a library that opens
|
|
106
|
+
its own raw socket still needs the native Node path or a general WISP/TCP relay.
|
|
107
|
+
|
|
108
|
+
## Python thread offloads
|
|
109
|
+
|
|
110
|
+
The bundled pthread interpreter supports standard `asyncio.to_thread`,
|
|
111
|
+
`run_in_executor`, and AnyIO's thread pool, including synchronous FastAPI routes.
|
|
112
|
+
These execute on real Python worker threads, preserving context propagation,
|
|
113
|
+
exceptions, and cancellation of the awaiting task.
|
|
114
|
+
|
|
115
|
+
Native socket calls release the GIL and send requests directly from each pthread
|
|
116
|
+
to the process supervisor over a private BroadcastChannel. Every outstanding
|
|
117
|
+
request owns a shared response buffer, so a waiting interpreter cannot prevent
|
|
118
|
+
another thread from completing network I/O. The supervisor still applies the
|
|
119
|
+
same descriptor ownership and outbound network policy.
|
|
120
|
+
|
|
121
|
+
Emscripten filesystem operations still proxy through the interpreter thread.
|
|
122
|
+
While other Python threads exist, the asyncio selector limits each wait to
|
|
123
|
+
10 ms and briefly yields, allowing those queued operations to run. This prevents
|
|
124
|
+
the old deadlock between a sleeping selector and a worker trying to wake it.
|
|
125
|
+
It adds polling overhead while the thread pool is alive.
|
|
126
|
+
|
|
127
|
+
Deploy the matching `python-worker.js` and the complete `python/` runtime directory
|
|
128
|
+
together. The worker detects the native bridge before enabling this behavior.
|
|
129
|
+
Older custom interpreter images retain the previous explicit `to_thread` error
|
|
130
|
+
and inline AnyIO fallback rather than silently entering a known deadlock.
|
|
131
|
+
|
|
132
|
+
Regression coverage includes context variables, exceptions, cancellation, file
|
|
133
|
+
I/O, simultaneous thread HTTP calls into an asyncio server, executor shutdown,
|
|
134
|
+
and FastAPI synchronous endpoints. The browser fixture also offloads a file read
|
|
135
|
+
and an outbound HTTPX request from a FastAPI streaming endpoint. This does not
|
|
136
|
+
certify arbitrary native extensions or indefinite blocking filesystem operations;
|
|
137
|
+
thread churn and long-running cleanup soak remain release gates. Browser egress
|
|
138
|
+
restrictions still apply independently of threading.
|
|
@@ -13,7 +13,7 @@ are proposed targets, not measured results.
|
|
|
13
13
|
| M5 | native packages | Pydantic core and one small extension survive repeated import/exit | **done for the current ABI** — C and PyO3 side-module wheels load through ordinary CPython imports; `pydantic==2.9.0` selects and runs with `pydantic-core==2.23.2` |
|
|
14
14
|
| M6 | local networking | echo server, concurrent connections, timeouts, port reuse | **partial** — the host-owned virtual TCP stack (`src/net/virtual-socket.ts`), its socket ABI family (`1792`), and router integration are done and cover `box.request()` to a non-JS listener plus cross-stack `EADDRINUSE` (`test/virtual-socket.test.ts`, `test/python-socket-abi.test.ts`, `test/universal-server.test.ts`); the CPython `_sbx_socket` adapter and `socket`/`selectors` patches are written but not yet built into the interpreter, so no Python process has bound a port through it |
|
|
15
15
|
| M7 | web serving | FastAPI validation, streaming, WebSocket, shutdown | **partial** — a real FastAPI/Pydantic 2 application validates and answers through ASGI; Uvicorn cannot yet serve through `box.request()` |
|
|
16
|
-
| M8 | threads | sync endpoints, AnyIO operations, thread cleanup | **partial** — pthread CPython, `asyncio.to_thread()`, and
|
|
16
|
+
| M8 | threads | sync endpoints, AnyIO operations, thread cleanup | **partial** — pthread CPython, `asyncio.to_thread()`, context/cancellation/file I/O, concurrent thread sockets, executor shutdown, and synchronous FastAPI endpoints pass; browser full-stack coverage is in `test/fixtures/fullstack-browser`; churn/cleanup soak is outstanding |
|
|
17
17
|
| M9 | persistence | recovery after an interrupted write or install | not started |
|
|
18
18
|
| M10 | ML | correct inference, bounded memory, cancellation | not started |
|
|
19
19
|
| M11 | source builds | compile, install and import a representative C extension in-browser | not started |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandboxedjs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "A Linux-like container that runs entirely inside Node.js — POSIX shell, ~140 coreutils, Node.js and Python runtimes, virtual filesystem and networking. No Docker, no VM, no native modules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"process": "^0.11.10",
|
|
95
95
|
"querystring-es3": "^0.2.1",
|
|
96
96
|
"resolve.exports": "^2.0.3",
|
|
97
|
+
"sandboxedjs": "^0.2.12",
|
|
97
98
|
"semver": "^7.8.5",
|
|
98
99
|
"set-cookie-parser": "^3.1.2",
|
|
99
100
|
"stream-browserify": "^3.0.0",
|