mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 21:32:05 +00:00
## Summary Refactors `Subprocess` to use explicit strong/weak reference management via `JSRef` instead of the `hasPendingActivity` mechanism that relies on JSC's internal `WeakHandleOwner`. ## Changes ### Core Refactoring - **JSRef.zig**: Added `update()` method to update references in-place - **subprocess.zig**: Changed `this_jsvalue: JSValue` to `this_value: JSRef` - **subprocess.zig**: Renamed `hasPendingActivityNonThreadsafe()` to `computeHasPendingActivity()` - **subprocess.zig**: Updated `updateHasPendingActivity()` to upgrade/downgrade `JSRef` based on pending activity - **subprocess.zig**: Removed `hasPendingActivity()` C callback function - **subprocess.zig**: Updated `finalize()` to call `this_value.finalize()` - **BunObject.classes.ts**: Set `hasPendingActivity: false` for Subprocess - **Writable.zig**: Updated references from `this_jsvalue` to `this_value.tryGet()` - **ipc.zig**: Updated references from `this_jsvalue` to `this_value.tryGet()` ## How It Works **Before**: Used `hasPendingActivity: true` which created a `JSC::Weak` reference with a `JSC::WeakHandleOwner` that kept the object alive as long as the C callback returned true. **After**: Uses `JSRef` with explicit lifecycle management: 1. Starts with a **weak** reference when subprocess is created 2. Immediately calls `updateHasPendingActivity()` after creation 3. **Upgrades to strong** reference when `computeHasPendingActivity()` returns true: - Subprocess hasn't exited - Has active stdio streams - Has active IPC connection 4. **Downgrades to weak** reference when all activity completes 5. GC can collect the subprocess once it's weak and no other references exist ## Benefits - Explicit control over subprocess lifecycle instead of relying on JSC's internal mechanisms - Clearer semantics: strong reference = "keep alive", weak reference = "can be GC'd" - Removes dependency on `WeakHandleOwner` callback overhead ## Testing - ✅ `test/js/bun/spawn/spawn.ipc.test.ts` - All 4 tests pass - ✅ `test/js/bun/spawn/spawn-stress.test.ts` - All tests pass (100 iterations) - ⚠️ `test/js/bun/spawn/spawnSync.test.ts` - 3/6 pass (3 pre-existing timing-based failures unrelated to this change) Manual testing confirms: - Subprocess is kept alive without user reference while running - Subprocess can be GC'd after completion - IPC keeps subprocess alive correctly - No crashes or memory leaks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
128 lines
2.4 KiB
TypeScript
128 lines
2.4 KiB
TypeScript
import { define } from "../../codegen/class-definitions";
|
|
|
|
export default [
|
|
define({
|
|
name: "ResourceUsage",
|
|
construct: true,
|
|
noConstructor: true,
|
|
finalize: true,
|
|
configurable: false,
|
|
hasPendingActivity: false,
|
|
klass: {},
|
|
JSType: "0b11101110",
|
|
proto: {
|
|
maxRSS: {
|
|
getter: "getMaxRSS",
|
|
},
|
|
shmSize: {
|
|
getter: "getSharedMemorySize",
|
|
},
|
|
swapCount: {
|
|
getter: "getSwapCount",
|
|
},
|
|
messages: {
|
|
getter: "getMessages",
|
|
},
|
|
signalCount: {
|
|
getter: "getSignalCount",
|
|
},
|
|
contextSwitches: {
|
|
getter: "getContextSwitches",
|
|
cache: true,
|
|
},
|
|
cpuTime: {
|
|
getter: "getCPUTime",
|
|
cache: true,
|
|
},
|
|
ops: {
|
|
getter: "getOps",
|
|
cache: true,
|
|
},
|
|
},
|
|
values: [],
|
|
}),
|
|
define({
|
|
name: "Subprocess",
|
|
construct: true,
|
|
noConstructor: true,
|
|
finalize: true,
|
|
configurable: false,
|
|
memoryCost: true,
|
|
klass: {},
|
|
JSType: "0b11101110",
|
|
proto: {
|
|
pid: {
|
|
getter: "getPid",
|
|
},
|
|
stdin: {
|
|
getter: "getStdin",
|
|
cache: true,
|
|
},
|
|
stdout: {
|
|
getter: "getStdout",
|
|
cache: true,
|
|
},
|
|
stderr: {
|
|
getter: "getStderr",
|
|
cache: true,
|
|
},
|
|
writable: {
|
|
getter: "getStdin",
|
|
cache: "stdin",
|
|
},
|
|
readable: {
|
|
getter: "getStdout",
|
|
cache: "stdout",
|
|
},
|
|
ref: {
|
|
fn: "doRef",
|
|
length: 0,
|
|
},
|
|
unref: {
|
|
fn: "doUnref",
|
|
length: 0,
|
|
},
|
|
resourceUsage: {
|
|
fn: "resourceUsage",
|
|
length: 0,
|
|
},
|
|
send: {
|
|
fn: "doSend",
|
|
length: 1,
|
|
},
|
|
kill: {
|
|
fn: "kill",
|
|
length: 1,
|
|
},
|
|
disconnect: {
|
|
fn: "disconnect",
|
|
length: 0,
|
|
},
|
|
connected: {
|
|
getter: "getConnected",
|
|
},
|
|
"@@asyncDispose": {
|
|
fn: "asyncDispose",
|
|
length: 1,
|
|
},
|
|
killed: {
|
|
getter: "getKilled",
|
|
},
|
|
exitCode: {
|
|
getter: "getExitCode",
|
|
},
|
|
signalCode: {
|
|
getter: "getSignalCode",
|
|
},
|
|
exited: {
|
|
getter: "getExited",
|
|
this: true,
|
|
},
|
|
stdio: {
|
|
getter: "getStdio",
|
|
},
|
|
},
|
|
values: ["exitedPromise", "onExitCallback", "onDisconnectCallback", "ipcCallback"],
|
|
}),
|
|
];
|