Skip to content

Commit 07534e2

Browse files
authored
refactor: drop QuickJS process host (#33)
1 parent 364e6c4 commit 07534e2

35 files changed

Lines changed: 147 additions & 1026 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@execbox/quickjs": minor
3+
---
4+
5+
Remove `QuickJsExecutor({ host: "process" })` and its process-hosted runner entrypoint. Use `host: "worker"` for local hosted QuickJS execution, or `@execbox/remote` for app-owned process, container, or VM boundaries.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ Portable code execution for [Model Context Protocol](https://modelcontextprotoco
1010

1111
</div>
1212

13-
Execbox turns host tool catalogs into callable guest namespaces, supports MCP wrapping on both sides of the boundary, and lets you place guest JavaScript where it fits your deployment: inline, behind a worker or child-process host, or across your own remote transport.
13+
Execbox turns host tool catalogs into callable guest namespaces, supports MCP wrapping on both sides of the boundary, and lets you place guest JavaScript where it fits your deployment: inline, behind a worker host, or across your own remote transport.
1414

1515
## Package Map
1616

1717
| Package | npm | What it is for |
1818
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
1919
| [`@execbox/core`](./packages/core/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fcore?style=flat-square)](https://www.npmjs.com/package/@execbox/core) | Core execution contract, provider resolution, MCP adapters, and runtime/protocol subpaths |
20-
| [`@execbox/quickjs`](./packages/quickjs/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fquickjs?style=flat-square)](https://www.npmjs.com/package/@execbox/quickjs) | QuickJS executor for inline, worker, and process hosts |
20+
| [`@execbox/quickjs`](./packages/quickjs/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fquickjs?style=flat-square)](https://www.npmjs.com/package/@execbox/quickjs) | QuickJS executor for inline and worker hosts |
2121
| [`@execbox/remote`](./packages/remote/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fremote?style=flat-square)](https://www.npmjs.com/package/@execbox/remote) | Advanced transport-backed executor for app-owned runtime boundaries |
2222

2323
## Examples

benchmarks/benchmark.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Comprehensive execbox performance benchmark suite.
33
*
44
* Covers:
5-
* 1. Single-execution latency across all in-process & out-of-process executors
5+
* 1. Single-execution latency across inline and worker-hosted executors
66
* 2. Cold-start vs warm (pooled/prewarmed) first-execution cost
77
* 3. Tool-call overhead scaling (0, 1, 5, 10 tool calls)
88
* 4. Schema validation overhead (with vs without input/output schemas)
@@ -349,7 +349,7 @@ async function suiteToolCalls(): Promise<void> {
349349
).join(";\n");
350350
}
351351

352-
// Use quickjs (fastest) and worker-pooled (representative out-of-process)
352+
// Use quickjs (fastest) and worker-pooled (representative hosted local mode)
353353
const testFactories = factories.filter(
354354
(f) => f.name === "quickjs (in-process)" || f.name === "worker (pooled)",
355355
);
@@ -507,31 +507,29 @@ async function suiteContention(): Promise<void> {
507507
const poolSizes = [1, 2, 4];
508508
const concurrency = 8;
509509

510-
for (const executor_type of ["worker", "process"] as const) {
511-
console.log(`\n [${executor_type}]`);
510+
console.log("\n [worker]");
512511

513-
for (const poolSize of poolSizes) {
514-
const executor = createContentionExecutor(executor_type, poolSize);
515-
try {
516-
await executor.prewarm?.(poolSize);
517-
const { totalMs, perRequestMs } = await benchConcurrent(
518-
executor,
519-
code,
520-
[simpleProvider],
521-
concurrency,
522-
totalRuns,
523-
);
524-
const throughput = (totalRuns / totalMs) * 1000;
525-
console.log(
526-
` pool_size=${String(poolSize).padEnd(2)} concurrency=${concurrency} ` +
527-
`throughput=${throughput.toFixed(1)} exec/s ` +
528-
`median=${fmt(median(perRequestMs))} ` +
529-
`p95=${fmt(percentile(perRequestMs, 95))} ` +
530-
`max=${fmt(max(perRequestMs))}`,
531-
);
532-
} finally {
533-
await executor.dispose?.();
534-
}
512+
for (const poolSize of poolSizes) {
513+
const executor = createContentionExecutor(poolSize);
514+
try {
515+
await executor.prewarm?.(poolSize);
516+
const { totalMs, perRequestMs } = await benchConcurrent(
517+
executor,
518+
code,
519+
[simpleProvider],
520+
concurrency,
521+
totalRuns,
522+
);
523+
const throughput = (totalRuns / totalMs) * 1000;
524+
console.log(
525+
` pool_size=${String(poolSize).padEnd(2)} concurrency=${concurrency} ` +
526+
`throughput=${throughput.toFixed(1)} exec/s ` +
527+
`median=${fmt(median(perRequestMs))} ` +
528+
`p95=${fmt(percentile(perRequestMs, 95))} ` +
529+
`max=${fmt(max(perRequestMs))}`,
530+
);
531+
} finally {
532+
await executor.dispose?.();
535533
}
536534
}
537535
}

benchmarks/config.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,6 @@ export function createBenchmarkFactories(): ExecutorFactory[] {
7070
}) as DisposableExecutor,
7171
supportsExplicitPrewarm: true,
7272
},
73-
{
74-
name: "process (ephemeral)",
75-
create: () =>
76-
new QuickJsExecutor({
77-
host: "process",
78-
mode: "ephemeral",
79-
}) as DisposableExecutor,
80-
supportsExplicitPrewarm: false,
81-
},
82-
{
83-
name: "process (pooled)",
84-
create: () =>
85-
new QuickJsExecutor({
86-
host: "process",
87-
...createPooledBenchmarkOptions(),
88-
}) as DisposableExecutor,
89-
supportsExplicitPrewarm: true,
90-
},
9173
];
9274
}
9375

@@ -98,12 +80,7 @@ export function createMemoryBenchmarkFactories(
9880
return factories.filter((factory) => allowedNames.has(factory.name));
9981
}
10082

101-
export function createContentionExecutor(
102-
executorType: "worker" | "process",
103-
poolSize: number,
104-
): DisposableExecutor {
83+
export function createContentionExecutor(poolSize: number): DisposableExecutor {
10584
const pool = createContentionPoolOptions(poolSize);
106-
return executorType === "worker"
107-
? (new QuickJsExecutor({ host: "worker", pool }) as DisposableExecutor)
108-
: (new QuickJsExecutor({ host: "process", pool }) as DisposableExecutor);
85+
return new QuickJsExecutor({ host: "worker", pool }) as DisposableExecutor;
10986
}

benchmarks/results.md

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@ This file records the current local benchmark run. Treat the tables as measured
1616
| QuickJS (in-process) | **1.47ms** | **2.71ms** | **3.94ms** |
1717
| Worker (ephemeral) | 62.40ms | 64.16ms | 65.67ms |
1818
| Worker (pooled) | 1.53ms | 2.81ms | 4.10ms |
19-
| Process (ephemeral) | 202.37ms | 204.10ms | 207.70ms |
20-
| Process (pooled) | 1.61ms | 2.92ms | 4.18ms |
2119

2220
### Notes
2321

2422
- On this machine, warmed pooled executors stayed close to QuickJS for trivial scripts.
25-
- Ephemeral executors remained far slower than pooled executors because each execution still pays worker or process startup cost.
26-
- Process pooled stayed low-latency in median terms, but its process startup path remained much more expensive than worker startup.
23+
- Ephemeral executors remained far slower than pooled executors because each execution still pays worker startup cost.
2724

2825
---
2926

@@ -36,12 +33,10 @@ Only pooled executors expose explicit `prewarm()`. QuickJS and ephemeral executo
3633
| QuickJS (in-process) | 1.46ms | N/A | N/A |
3734
| Worker (ephemeral) | 62.70ms | N/A | N/A |
3835
| Worker (pooled) | 62.30ms | 1.81ms | 97.1% |
39-
| Process (ephemeral) | 202.51ms | N/A | N/A |
40-
| Process (pooled) | 202.31ms | 2.32ms | 98.9% |
4136

4237
### Notes
4338

44-
- True `prewarm()` delivered the intended first-request behavior in this run: pooled worker and pooled process executors dropped from shell-plus-guest startup latency to low-single-digit warm execution latency.
39+
- True `prewarm()` delivered the intended first-request behavior in this run: pooled worker executors dropped from shell-plus-guest startup latency to low-single-digit warm execution latency.
4540
- `prewarm()` pays the host-shell and guest-startup path before live traffic arrives.
4641

4742
---
@@ -92,13 +87,11 @@ The pooled benchmark factories in this suite use a fixed `pool.maxSize: 2`.
9287
| -------------------- | --------------- | ------ | ------ | ---------- |
9388
| QuickJS (in-process) | **395.3** | 684.6 | 1078.8 | **1635.1** |
9489
| Worker (pooled) | 356.5 | 722.9 | 706.4 | 749.8 |
95-
| Process (pooled) | 352.3 | 653.2 | 634.5 | 675.1 |
9690

9791
### Notes
9892

9993
- QuickJS stayed the strongest path for trusted, in-process workloads at low and high concurrency in this run, while worker pooled edged ahead at concurrency 2.
100-
- Worker and process pooled executors paid visible queueing once demand moved past the benchmark pool size.
101-
- Process pooled stayed competitive, but it still trailed worker pooled at every tested concurrency level.
94+
- Worker pooled executors paid visible queueing once demand moved past the benchmark pool size.
10295

10396
---
10497

@@ -109,21 +102,16 @@ The pooled benchmark factories in this suite use a fixed `pool.maxSize: 2`.
109102
| Worker | 1 | 362.6 | 22.18ms | 22.51ms | 22.53ms |
110103
| Worker | 2 | 704.2 | 10.97ms | 11.64ms | 12.39ms |
111104
| Worker | 4 | **1327.2** | 5.60ms | 6.95ms | 6.99ms |
112-
| Process | 1 | 351.0 | 22.54ms | 23.66ms | 23.97ms |
113-
| Process | 2 | 693.3 | 11.02ms | 12.95ms | 12.99ms |
114-
| Process | 4 | 1111.3 | 6.50ms | 8.73ms | 8.75ms |
115105

116106
### Notes
117107

118-
- Increasing pool size improved both worker and process executors in this run.
119-
- Worker pooled still kept the better mix of throughput and tail latency at every pool size tested.
120-
- Process pooled also scaled up well here, but its tails remained wider than worker pooled at the same pool size.
108+
- Increasing pool size improved worker throughput and tail latency in this run.
121109

122110
---
123111

124112
## 7. Host-Process Memory Delta
125113

126-
This suite only measures the parent Node process. It does not attempt to attribute child-process RSS back to `QuickJsExecutor({ host: "process" })`.
114+
This suite only measures the parent Node process.
127115

128116
| Executor | Heap Delta | RSS Delta | External Delta |
129117
| -------------------- | ---------- | --------- | -------------- |
@@ -144,13 +132,11 @@ This suite only measures the parent Node process. It does not attempt to attribu
144132
### High-value takeaways from this snapshot
145133

146134
- QuickJS remained the lowest-latency option for trusted, in-process workloads on this machine.
147-
- True `prewarm()` delivered the intended first-request benefit for pooled worker and pooled process executors.
135+
- True `prewarm()` delivered the intended first-request benefit for pooled worker executors.
148136
- Worker pooled remained the strongest general-purpose local trade-off between isolation, throughput, and tail latency.
149-
- Process pooled stayed viable when process isolation matters, but it still trailed worker pooled on throughput and tail latency.
150137
- Ephemeral modes remained dramatically slower than pooled modes and are best reserved for cases that need a fresh host boundary per execution.
151138

152139
### What this snapshot does not prove
153140

154141
- It does not prove exact throughput rankings for every workload or host. The concurrency and tool-call suites are still sensitive to local scheduler noise.
155-
- It does not prove memory behavior for `QuickJsExecutor({ host: "process" })`, because the memory suite intentionally avoids reporting child-process RSS as if it were host-process memory.
156142
- It does not measure `RemoteExecutor`, because remote performance depends on the caller-owned transport and remote runtime deployment.

docs/architecture/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ flowchart LR
4040

4141
### Package Roles
4242

43-
| Package | Role |
44-
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
45-
| `@execbox/core` | App-facing core types, provider resolution, MCP adapters, plus runtime and protocol subpaths |
46-
| `@execbox/quickjs` | Default QuickJS executor package with inline, worker-hosted, and process-hosted modes plus a reusable runner |
47-
| `@execbox/remote` | Transport-backed executor that runs against an app-defined runner boundary |
43+
| Package | Role |
44+
| ------------------ | -------------------------------------------------------------------------------------------- |
45+
| `@execbox/core` | App-facing core types, provider resolution, MCP adapters, plus runtime and protocol subpaths |
46+
| `@execbox/quickjs` | Default QuickJS executor package with inline and worker-hosted modes plus a reusable runner |
47+
| `@execbox/remote` | Transport-backed executor that runs against an app-defined runner boundary |
4848

4949
## End-to-End Execution Model
5050

docs/architecture/execbox-core.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,12 @@ Executors are responsible for their own runtime-specific classification rules, b
189189

190190
## Why the Core Stays Small
191191

192-
The core package does not own QuickJS, worker threads, child processes, or transport mechanics. That separation keeps the core useful for:
192+
The core package does not own QuickJS, worker threads, process boundaries, or transport mechanics. That separation keeps the core useful for:
193193

194194
- direct in-process runtimes
195195
- worker-backed runtimes
196196
- MCP wrapper servers
197-
- process or remote execution models
197+
- remote execution models
198198

199199
The consequence is deliberate separation between:
200200

0 commit comments

Comments
 (0)