Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Bot
58cb805ed9 Implement Bun.{stdin,stderr,stdout} as LazyProperties on ZigGlobalObject
Previously, Bun.stdin, Bun.stderr, and Bun.stdout were created as regular
properties on the BunObject, which could result in multiple instances being
created within the same thread.

This commit refactors the implementation to use LazyProperties on
ZigGlobalObject to ensure single instances per thread:

**Changes:**
- Added LazyProperty declarations in ZigGlobalObject.h for m_bunStdin,
  m_bunStderr, m_bunStdout
- Added LazyProperty initialization calls in ZigGlobalObject constructor
- Updated BunObject property callbacks to delegate to LazyProperty system
- Removed stdin/stderr/stdout from FOR_EACH_GETTER macro in BunObject+exports.h
- Added manual wrapper functions in BunObject.cpp that call LazyProperty getters
- Updated BunObject.zig to export C-compatible initializer functions with
  callconv(.C)
- Added comprehensive test to verify single instance behavior

**Technical Implementation:**
- Properties are now accessed via `zigGlobalObject->bunStdin()` etc.
- Uses `.getInitializedOnMainThread()` to ensure thread-safe single instances
- Zig functions use `callconv(.C)` for proper C++ interop
- Maintains backward compatibility - `Bun.stdin`, `Bun.stderr`, `Bun.stdout`
  still work as expected

**Testing:**
- Added test verifying multiple accesses return same object instances
- Verified Blob functionality still works correctly
- All existing tests continue to pass

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 02:47:32 +00:00
Claude Bot
67baa858e2 Implement Bun.{stdin,stderr,stdout} as LazyProperties to prevent multiple instances
Previously, accessing Bun.stdin, Bun.stderr, or Bun.stdout multiple times could potentially create
multiple instances within the same thread, which could lead to memory waste and inconsistent behavior.

This change implements these properties as LazyProperties on ZigGlobalObject, ensuring:
- Single instance per stream per thread
- Thread-safe lazy initialization
- Consistent object identity across multiple accesses
- Maintained functionality as Blob objects

Changes:
- Add LazyPropertyOfGlobalObject declarations for m_bunStdin, m_bunStderr, m_bunStdout in ZigGlobalObject.h
- Create Zig initializer functions with C calling convention in BunObject.zig
- Add C++ wrapper functions that use LazyProperty.getInitializedOnMainThread()
- Initialize LazyProperties with initLater() in ZigGlobalObject constructor
- Add comprehensive test coverage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 02:04:04 +00:00
6 changed files with 123 additions and 41 deletions

View File

@@ -72,9 +72,6 @@ pub const BunObject = struct {
pub const inspect = toJSLazyPropertyCallback(Bun.getInspect);
pub const origin = toJSLazyPropertyCallback(Bun.getOrigin);
pub const semver = toJSLazyPropertyCallback(Bun.getSemver);
pub const stderr = toJSLazyPropertyCallback(Bun.getStderr);
pub const stdin = toJSLazyPropertyCallback(Bun.getStdin);
pub const stdout = toJSLazyPropertyCallback(Bun.getStdout);
pub const unsafe = toJSLazyPropertyCallback(Bun.getUnsafe);
pub const S3Client = toJSLazyPropertyCallback(Bun.getS3ClientConstructor);
pub const s3 = toJSLazyPropertyCallback(Bun.getS3DefaultClient);
@@ -139,9 +136,6 @@ pub const BunObject = struct {
@export(&BunObject.hash, .{ .name = lazyPropertyCallbackName("hash") });
@export(&BunObject.inspect, .{ .name = lazyPropertyCallbackName("inspect") });
@export(&BunObject.origin, .{ .name = lazyPropertyCallbackName("origin") });
@export(&BunObject.stderr, .{ .name = lazyPropertyCallbackName("stderr") });
@export(&BunObject.stdin, .{ .name = lazyPropertyCallbackName("stdin") });
@export(&BunObject.stdout, .{ .name = lazyPropertyCallbackName("stdout") });
@export(&BunObject.unsafe, .{ .name = lazyPropertyCallbackName("unsafe") });
@export(&BunObject.semver, .{ .name = lazyPropertyCallbackName("semver") });
@export(&BunObject.embeddedFiles, .{ .name = lazyPropertyCallbackName("embeddedFiles") });
@@ -188,6 +182,12 @@ pub const BunObject = struct {
@export(&BunObject.zstdDecompress, .{ .name = callbackName("zstdDecompress") });
// --- Callbacks ---
// --- LazyProperty initializers ---
@export(&createBunStdin, .{ .name = "BunObject__createBunStdin" });
@export(&createBunStderr, .{ .name = "BunObject__createBunStderr" });
@export(&createBunStdout, .{ .name = "BunObject__createBunStdout" });
// --- LazyProperty initializers ---
// --- Getters ---
@export(&BunObject.main, .{ .name = "BunObject_getter_main" });
// --- Getters ---
@@ -559,38 +559,6 @@ pub fn getOrigin(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue
return ZigString.init(VirtualMachine.get().origin.origin).toJS(globalThis);
}
pub fn getStdin(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdin();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
pub fn getStderr(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stderr();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
pub fn getStdout(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdout();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
pub fn enableANSIColors(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
_ = globalThis;
@@ -2083,6 +2051,40 @@ const Environment = bun.Environment;
const MutableString = bun.MutableString;
const Output = bun.Output;
const assert = bun.assert;
// LazyProperty initializers for stdin/stderr/stdout
pub fn createBunStdin(globalThis: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdin();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
pub fn createBunStderr(globalThis: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stderr();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
pub fn createBunStdout(globalThis: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdout();
store.ref();
var blob = jsc.WebCore.Blob.new(
jsc.WebCore.Blob.initWithStore(store, globalThis),
);
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
const default_allocator = bun.default_allocator;
const strings = bun.strings;
const SemverObject = bun.Semver.SemverObject;

View File

@@ -31,9 +31,6 @@
macro(origin) \
macro(s3) \
macro(semver) \
macro(stderr) \
macro(stdin) \
macro(stdout) \
macro(unsafe) \
macro(valkey) \

View File

@@ -84,6 +84,7 @@ namespace Bun {
extern "C" bool has_bun_garbage_collector_flag_enabled;
static JSValue BunObject_lazyPropCb_wrap_ArrayBufferSink(VM& vm, JSObject* bunObject)
{
return jsCast<Zig::GlobalObject*>(bunObject->globalObject())->ArrayBufferSink();
@@ -875,6 +876,25 @@ static JSC_DEFINE_CUSTOM_SETTER(setBunObjectMain, (JSC::JSGlobalObject * globalO
#define bunObjectReadableStreamToJSONCodeGenerator WebCore::readableStreamReadableStreamToJSONCodeGenerator
#define bunObjectReadableStreamToTextCodeGenerator WebCore::readableStreamReadableStreamToTextCodeGenerator
// LazyProperty wrappers for stdin/stderr/stdout
static JSValue BunObject_lazyPropCb_wrap_stdin(VM& vm, JSObject* bunObject)
{
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(bunObject->globalObject());
return zigGlobalObject->m_bunStdin.getInitializedOnMainThread(zigGlobalObject);
}
static JSValue BunObject_lazyPropCb_wrap_stderr(VM& vm, JSObject* bunObject)
{
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(bunObject->globalObject());
return zigGlobalObject->m_bunStderr.getInitializedOnMainThread(zigGlobalObject);
}
static JSValue BunObject_lazyPropCb_wrap_stdout(VM& vm, JSObject* bunObject)
{
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(bunObject->globalObject());
return zigGlobalObject->m_bunStdout.getInitializedOnMainThread(zigGlobalObject);
}
#include "BunObject.lut.h"
#undef bunObjectReadableStreamToArrayCodeGenerator
@@ -905,6 +925,7 @@ static JSValue constructSecretsObject(VM& vm, JSObject* bunObject)
return Bun::createSecretsObject(vm, zigGlobalObject);
}
JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject)
{
return JSBunObject::create(vm, jsCast<Zig::GlobalObject*>(globalObject));

View File

@@ -330,6 +330,11 @@ extern "C" void* Bun__getVM();
extern "C" void Bun__setDefaultGlobalObject(Zig::GlobalObject* globalObject);
// Declare the Zig functions for LazyProperty initializers
extern "C" JSC::EncodedJSValue BunObject__createBunStdin(JSC::JSGlobalObject*);
extern "C" JSC::EncodedJSValue BunObject__createBunStderr(JSC::JSGlobalObject*);
extern "C" JSC::EncodedJSValue BunObject__createBunStdout(JSC::JSGlobalObject*);
static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites)
{
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -3463,6 +3468,17 @@ void GlobalObject::finishCreation(VM& vm)
init.set(JSC::JSBigInt64Array::create(init.owner, JSC::JSBigInt64Array::createStructure(init.vm, init.owner, init.owner->objectPrototype()), 7));
});
// Initialize LazyProperties for stdin/stderr/stdout
m_bunStdin.initLater([](const LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
init.set(JSC::JSValue::decode(BunObject__createBunStdin(init.owner)).getObject());
});
m_bunStderr.initLater([](const LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
init.set(JSC::JSValue::decode(BunObject__createBunStderr(init.owner)).getObject());
});
m_bunStdout.initLater([](const LazyProperty<JSC::JSGlobalObject, JSC::JSObject>::Initializer& init) {
init.set(JSC::JSValue::decode(BunObject__createBunStdout(init.owner)).getObject());
});
configureNodeVM(vm, this);
#if ENABLE(REMOTE_INSPECTOR)

View File

@@ -621,6 +621,10 @@ public:
V(public, LazyPropertyOfGlobalObject<Structure>, m_JSBunRequestStructure) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_JSBunRequestParamsPrototype) \
\
V(public, LazyPropertyOfGlobalObject<JSObject>, m_bunStdin) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_bunStderr) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_bunStdout) \
\
V(public, LazyPropertyOfGlobalObject<Structure>, m_JSNodeHTTPServerSocketStructure) \
V(public, LazyPropertyOfGlobalObject<JSFloat64Array>, m_statValues) \
V(public, LazyPropertyOfGlobalObject<JSBigInt64Array>, m_bigintStatValues) \
@@ -654,6 +658,11 @@ public:
JSObject* nodeErrorCache() const { return m_nodeErrorCache.getInitializedOnMainThread(this); }
// LazyProperty accessors for stdin/stderr/stdout
JSC::JSObject* bunStdin() const { return m_bunStdin.getInitializedOnMainThread(this); }
JSC::JSObject* bunStderr() const { return m_bunStderr.getInitializedOnMainThread(this); }
JSC::JSObject* bunStdout() const { return m_bunStdout.getInitializedOnMainThread(this); }
Structure* memoryFootprintStructure()
{
return m_memoryFootprintStructure.getInitializedOnMainThread(this);

View File

@@ -0,0 +1,37 @@
import { test, expect } from "bun:test";
test("Bun.stdin, stderr, stdout are LazyProperty instances (same object on multiple access)", () => {
// Test that multiple accesses return the same object instance
const stdin1 = Bun.stdin;
const stdin2 = Bun.stdin;
expect(stdin1).toBe(stdin2);
const stderr1 = Bun.stderr;
const stderr2 = Bun.stderr;
expect(stderr1).toBe(stderr2);
const stdout1 = Bun.stdout;
const stdout2 = Bun.stdout;
expect(stdout1).toBe(stdout2);
});
test("Bun.stdin, stderr, stdout are valid Blob instances", () => {
expect(Bun.stdin).toBeInstanceOf(Blob);
expect(Bun.stderr).toBeInstanceOf(Blob);
expect(Bun.stdout).toBeInstanceOf(Blob);
// Test they have expected properties
expect(typeof Bun.stdin.size).toBe("number");
expect(typeof Bun.stderr.size).toBe("number");
expect(typeof Bun.stdout.size).toBe("number");
expect(typeof Bun.stdin.type).toBe("string");
expect(typeof Bun.stderr.type).toBe("string");
expect(typeof Bun.stdout.type).toBe("string");
});
test("stdin, stderr, stdout objects are different from each other", () => {
expect(Bun.stdin).not.toBe(Bun.stderr);
expect(Bun.stdin).not.toBe(Bun.stdout);
expect(Bun.stderr).not.toBe(Bun.stdout);
});