Skip to content

Fix ReadableStream reference management for Request/Response bodies#23254

Closed
robobun wants to merge 24 commits into
mainfrom
jarred/readable-stream-strong
Closed

Fix ReadableStream reference management for Request/Response bodies#23254
robobun wants to merge 24 commits into
mainfrom
jarred/readable-stream-strong

Conversation

@robobun

@robobun robobun commented Oct 4, 2025

Copy link
Copy Markdown
Collaborator

Summary

Fixes critical issues with the new ReadableStream.Ref system for managing stream references in Request/Response bodies. This resolves hangs and crashes in body-stream tests caused by incorrect strong vs weak reference handling.

Problem

When Request.getBody() or Response.getBody() is called without a valid JSValue owner (e.g., when the Request/Response wrapper JSValue is .zero), the code was attempting to store streams in the GC cache, which failed silently. This led to:

  1. Streams not being stored anywhere (.set(.empty, ...) is a no-op)
  2. Later .get(owner).? calls panicking with "attempt to use null value"
  3. Body-stream tests hanging or crashing

Root Cause

The ReadableStream.Ref system uses different storage strategies:

  • Weak refs (.Request/.Response): Store stream JSValue in GC cache, retrieved via owner JSValue
  • Strong refs (.strong): Direct strong reference to stream JSValue

The code was always trying to use weak refs, even when no owner JSValue was available.

Changes

1. Body.zig - toReadableStream() blob conversion (lines 443-456)

  • Check if owner has valid JSValue before storing in GC cache
  • Use .strong ref when owner JSValue is .zero or owner is .empty
  • Only populate GC cache when owner has valid JSValue

2. Body.zig - toReadableStream() drain conversion (lines 502-519)

  • Same logic: use .strong ref when no valid owner JSValue
  • Fixes panics from unwrapping null on lines 504/507

3. Body.zig - tee() operation (line 1020)

  • Changed from using .strong owner to actual owner parameter
  • Ensures Ref type matches the owner for proper stream retrieval

4. ReadableStream.zig - Ref.init() (lines 127-142)

  • Return .empty for .strong/.empty owners (no stream value yet)
  • Caller uses .set() to populate with stream

5. test/js/web/fetch/body-stream.test.ts

  • Fixed test discovery hang by using lazy array creation
  • Changed from creating hundreds of 2MB arrays during discovery
  • Arrays now created on-demand when each test runs

Test Results

✅ All individual tests and filtered test subsets pass:

  • Request.prototype.text tests: 20 tests in 11s ✅
  • Request.prototype.bytes tests: 10 tests in 9.5s ✅
  • Request.prototype.arrayBuffer tests: 8 tests in 8.5s ✅
  • 1MB streaming tests: 32 tests in 102s ✅

⚠️ Full suite (4,883 tests) takes ~4 hours due to large streaming tests being slow (~3s each for 1-2MB payloads)

Impact

  • Fixes crashes and hangs in body stream handling
  • Proper memory management for Request/Response body streams
  • Correct strong vs weak reference usage based on owner availability

🤖 Generated with Claude Code

Jarred-Sumner and others added 7 commits September 30, 2025 21:50
This commit fixes several issues with the new ReadableStream.Ref system
that caused body-stream tests to hang:

1. **Body.zig:1020** - Fixed owner mismatch when setting stream after draining
   - Was using `.strong` owner when should use actual owner (Request/Response)
   - This caused Ref.get() to fail since Ref type didn't match owner type

2. **Body.zig:964,1026** - Set proper Ref type for cloned bodies
   - When tee() returns streams via array, cloned body gets proper Ref type
   - Ensures cloned body can access its stream via GC cache

3. **Request.zig:819** - Fixed stream assignment in doClone
   - Stream[0] should go to original request, not cloned request

4. **ReadableStream.zig:108** - Always update original Ref after tee()
   - Original body's Ref must point to its tee'd stream

5. **body-stream.test.ts** - Fixed test discovery hang
   - Changed from eager to lazy array creation using factory function
   - Prevents creating hundreds of 2MB arrays during test discovery
   - Arrays now created on-demand when each test runs

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

Co-Authored-By: Claude <noreply@anthropic.com>
…alue

The issue was that when Request.getBody() or Response.getBody() is called
without a valid JSValue owner (e.g., owner.Request == .zero), the code was
trying to store streams in the GC cache, which failed silently, leading to
null reference panics and hanging tests.

Changes:

1. **Body.zig:443-456** - When converting blob to stream in toReadableStream()
   - Check if owner has valid JSValue
   - Use .strong ref if JSValue is .zero or owner is .empty
   - Only set GC cache if owner has valid JSValue

2. **Body.zig:502-519** - When creating stream from drain in toReadableStream()
   - Same logic: use .strong if no valid owner JSValue
   - Fixes panics from unwrapping null on lines 504/507

3. **ReadableStream.zig:127-142** - Fix Ref.init() for empty/strong owners
   - Return .empty instead of trying to create .strong without a stream
   - Caller should use .set() to populate

This fixes the body-stream.test.ts hang. The tests now pass but the full
suite (4,883 tests) takes a very long time (~3s/test for large streaming
tests = ~4 hours total). Individual test subsets all pass correctly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the claude label Oct 4, 2025
@robobun

robobun commented Oct 4, 2025

Copy link
Copy Markdown
Collaborator Author
Updated 2:37 PM PT - Oct 6th, 2025

@cirospaciari, your commit 35338e6 has 8 failures in Build #28195 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 23254

That installs a local version of the PR into your bun-23254 executable, so you can run:

bun-23254 --bun

@coderabbitai

coderabbitai Bot commented Oct 4, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Threads an explicit ReadableStream Owner through Body, ReadableStream, Request, Response, fetch, server, bindings, and related call sites; adds Owner-aware Ref lifecycle and updates APIs/ABI to pass owner/readable_stream_tee for blob/stream operations.

Changes

Cohort / File(s) Summary
ReadableStream & Body core
src/bun.js/webcore/ReadableStream.zig, src/bun.js/webcore/Body.zig
Adds Ref/Owner types and owner-aware APIs; converts PendingValue.readable to Ref; threads Owner through size, toReadableStream, clone, tee, extract, abort, toAnyBlob and many Body/Value/PendingValue methods.
Request & Response ownership + classes
src/bun.js/webcore/Request.zig, src/bun.js/webcore/Response.zig, src/bun.js/webcore/response.classes.ts
Adds this_jsvalue weak refs; constructors accept thisValue; clone/cloneInto/cloneValue accept and propagate this_value and readable_stream_tee; class defs set constructNeedsThis: true.
Fetch, Blob, and stream handling
src/bun.js/webcore/fetch.zig, src/bun.js/webcore/Blob.zig, src/bun.js/webcore/BakeResponse.zig
Reworks Locked.readable handling to owner-aware Ref paths; updates fromJS/onBodyReceived and stream lifecycle to bind per-request/response owners; BakeResponse now passes explicit .zero.
Server, routing, and request context
src/bun.js/api/server/RequestContext.zig, src/bun.js/api/server.zig, src/bun.js/api/server/FileRoute.zig, src/bun.js/api/server/StaticRoute.zig
Propagates Owner into toBlobIfPossible/toReadableStream calls; updates readable_stream_ref init/deinit/abort and Request.cloneInto call sites; NewRequestContext gains ThisServer type parameter.
Bindings & JSGlobal integration
src/bun.js/bindings/JSBunRequest.cpp, src/bun.js/bindings/JSGlobalObject.zig
Extends Request__clone ABI to accept thisValue and readableStreamTee; synchronizes teed body pieces; uses owner-aware body extraction and toReadableStream variants.
API call-site updates
src/bun.js/api/bun/spawn/stdio.zig, src/bun.js/api/html_rewriter.zig
extractBodyValue and Body.extract call sites updated to accept/pass Owner; toReadableStream invocations now receive owner parameter.
Node types, shell, and tooling tweaks
src/bun.js/node/types.zig, src/shell/states/Cmd.zig, src/codegen/generate-classes.ts, src/codegen/generate-jssink.ts
Replaces owner-less blob/stream calls with owner-aware variants (Request uses empty owner, Response derives owner from this_jsvalue); shell redirection uses computed owner; codegen guards memoryCost/estimatedSize against null wrapped pointers.

Possibly related PRs

Suggested reviewers

  • Jarred-Sumner
  • taylordotfish
  • dylan-conway

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description does not use the required template headings and is missing the “What does this PR do?” and “How did you verify your code works?” sections, so it cannot be validated against the repository’s description template. Restructure the description to include the required sections by adding a “### What does this PR do?” summary of the change and a “### How did you verify your code works?” section detailing the testing steps used.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title clearly summarizes the primary change of fixing ReadableStream reference management for Request and Response bodies and is concise and specific to the main purpose of the PR.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jarred/readable-stream-strong

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/webcore/Response.zig (1)

543-577: Constructor never records JS owner

Response.this_jsvalue stays empty when we construct a response from JS, because the constructor never seeds the weak ref. Any later code that tries response.this_jsvalue.tryGet() (e.g. the new owner-aware toBlobIfPossible/abort paths in RequestContext) will therefore fall back to .empty, forcing strong refs and undoing the weak-owner fix this PR is chasing. We need to stash thisValue while we still have it—both in the S3 fast path and the general constructor path—so owner-aware logic can recover the JS wrapper.

Please initialize this_jsvalue inside the constructor, e.g.:

@@
-        var response: Response = .{
+        var response: Response = .{
             .init = Response.Init{
                 .status_code = 302,
             },
             .body = Body{
                 .value = .{ .Empty = {} },
             },
             .url = bun.String.empty,
         };
+        if (thisValue != .zero) {
+            response.this_jsvalue = .initWeak(thisValue);
+        }
@@
-    var response = bun.new(Response, Response{
+    var response = bun.new(Response, Response{
         .body = body,
         .init = init,
     });
+    if (thisValue != .zero) {
+        response.this_jsvalue = .initWeak(thisValue);
+    }

Also applies to: 626-634

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f0eb047 and 5f1c745.

📒 Files selected for processing (21)
  • src/bun.js/api/bun/spawn/stdio.zig (3 hunks)
  • src/bun.js/api/html_rewriter.zig (1 hunks)
  • src/bun.js/api/server.zig (1 hunks)
  • src/bun.js/api/server/FileRoute.zig (1 hunks)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
  • src/bun.js/api/server/StaticRoute.zig (1 hunks)
  • src/bun.js/bindings/JSBunRequest.cpp (2 hunks)
  • src/bun.js/bindings/JSGlobalObject.zig (1 hunks)
  • src/bun.js/node/types.zig (2 hunks)
  • src/bun.js/webcore/BakeResponse.zig (1 hunks)
  • src/bun.js/webcore/Blob.zig (2 hunks)
  • src/bun.js/webcore/Body.zig (42 hunks)
  • src/bun.js/webcore/ReadableStream.zig (2 hunks)
  • src/bun.js/webcore/Request.zig (14 hunks)
  • src/bun.js/webcore/Response.zig (10 hunks)
  • src/bun.js/webcore/fetch.zig (5 hunks)
  • src/bun.js/webcore/response.classes.ts (2 hunks)
  • src/codegen/generate-classes.ts (1 hunks)
  • src/codegen/generate-jssink.ts (1 hunks)
  • src/shell/states/Cmd.zig (1 hunks)
  • test/js/web/fetch/body-stream.test.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (17)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server.zig
  • src/shell/states/Cmd.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/ReadableStream.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server.zig
  • src/shell/states/Cmd.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/ReadableStream.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/api/server.zig
  • src/bun.js/api/server/RequestContext.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/server/FileRoute.zig
  • src/bun.js/api/server/StaticRoute.zig
  • src/bun.js/webcore/Blob.zig
  • src/bun.js/node/types.zig
  • src/bun.js/api/bun/spawn/stdio.zig
  • src/bun.js/bindings/JSGlobalObject.zig
  • src/bun.js/webcore/Response.zig
  • src/bun.js/api/server.zig
  • src/bun.js/webcore/BakeResponse.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/fetch.zig
  • src/bun.js/webcore/Body.zig
  • src/bun.js/webcore/ReadableStream.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/web/fetch/body-stream.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/web/fetch/body-stream.test.ts
test/js/web/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Web API tests under test/js/web/, separated by category

Files:

  • test/js/web/fetch/body-stream.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/web/fetch/body-stream.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/js/web/fetch/body-stream.test.ts
test/js/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests for specific features under test/js/ by module

Files:

  • test/js/web/fetch/body-stream.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or roll your own random port
Prefer normalizeBunSnapshot for snapshotting test output instead of asserting raw strings
Do not write tests that assert absence of crashes (e.g., 'no panic' or 'no uncaught exception')
Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests
Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead
Prefer running tests via bun bd test and use provided harness utilities (bunEnv, bunExe, tempDir)
Use Bun.spawn with proper stdio handling and await proc.exited in process-spawning tests

Files:

  • test/js/web/fetch/body-stream.test.ts
test/js/web/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Web API tests under test/js/web/

Files:

  • test/js/web/fetch/body-stream.test.ts
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/JSBunRequest.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/JSBunRequest.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/JSBunRequest.cpp
**/*.classes.ts

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

**/*.classes.ts: Define JavaScript class interfaces in .classes.ts using define({ name, constructor, JSType, finalize, proto: { ... } })
Use cache: true on properties that should use JSC WriteBarrier-backed caching

Files:

  • src/bun.js/webcore/response.classes.ts
src/**/*.classes.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Define JavaScriptCore class metadata in .classes.ts files to drive code generation

Files:

  • src/bun.js/webcore/response.classes.ts
🧠 Learnings (3)
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/JSGlobalObject.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern

Applied to files:

  • src/bun.js/webcore/Response.zig
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig
🧬 Code graph analysis (1)
src/bun.js/bindings/JSBunRequest.cpp (2)
src/js/builtins/ReadableStreamInternals.ts (1)
  • readableStreamTee (509-564)
src/bun.js/bindings/CookieMap.cpp (2)
  • clone (205-211)
  • clone (205-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (4)
src/bun.js/node/types.zig (1)

89-105: Correct owner-sensitive blob extraction

Passing .empty for owner when a Request body lacks a JS wrapper and deriving the Response owner from this_jsvalue aligns the blob conversion with the new Ref semantics, preventing weak-ref caching when the owner is unavailable. Looks solid.

src/bun.js/webcore/BakeResponse.zig (1)

47-48: Constructor call updated correctly

Forwarding .zero into Response.constructor keeps the SSR helper in sync with the new signature and avoids stale owner state. Nice catch.

src/bun.js/api/server.zig (1)

1194-1203: Clone forwarding keeps owner context

Threading the original Request JS value and null cache through cloneInto matches the revised API and preserves body ownership during server fetch cloning. Looks good.

src/codegen/generate-jssink.ts (1)

419-429: Null-safe memory accounting

Guarding both memoryCost and estimatedSize against null wrapped sinks avoids dereferencing freed controllers without altering behavior when a sink exists. Good defensive tweak.

Comment thread src/bun.js/bindings/JSBunRequest.cpp
Comment thread src/bun.js/webcore/Request.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/bun.js/webcore/ReadableStream.zig (1)

92-111: Correct tee implementation with detailed debug logging.

The logic properly handles the tee operation and updates the Ref to point to the first teed stream using the actual owner parameter (line 108), which aligns with the PR objectives. The multiple debug logs (lines 94, 99, 104, 107, 109) are helpful for diagnosing the hangs/crashes being fixed, though you may want to reduce their verbosity once the fix is stable.

Consider reducing debug log verbosity once the fix is proven stable in production:

-        bun.Output.debug("Ref.tee: get() returned null, this={s}, owner={s}", .{ @tagName(this.*), @tagName(owner) });
+        // Stream not available for tee
-        bun.Output.debug("Ref.tee: stream.tee() returned null", .{});
+        // Tee operation returned null
-        bun.Output.debug("Ref.tee: populated array with tee'd streams", .{});
-        bun.Output.debug("Ref.tee: calling this.set(), this was {s}, owner is {s}", .{ @tagName(this.*), @tagName(owner) });
-        bun.Output.debug("Ref.tee: after set(), this is now {s}", .{@tagName(this.*)});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5f1c745 and 24c7dbf.

📒 Files selected for processing (1)
  • src/bun.js/webcore/ReadableStream.zig (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/ReadableStream.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/ReadableStream.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/ReadableStream.zig
🔇 Additional comments (6)
src/bun.js/webcore/ReadableStream.zig (6)

6-25: Well-structured ownership model.

The Ref union with its nested Owner union and Type enum provides a clean API for managing ReadableStream ownership across different contexts (strong refs, Response/Request weak refs). The parallel structure makes the type system predictable.


26-51: Correct owner validation and error handling.

The method properly validates that the Ref variant matches the Owner type before accessing the GC cache (lines 30, 39). This defensive check prevents accessing the wrong cache or attempting invalid casts. The cascading optional checks with orelse null provide consistent error handling.


58-90: Fixes the core weak-ref issue.

This correctly implements the PR's main fix: when jsvalue == .zero (no valid owner), the code sets the Ref to .empty rather than attempting to use the weak-ref GC cache (lines 65-68, 75-78). The deinit() call before each reassignment ensures proper cleanup. This addresses the "attempt to use null value" panics described in the PR objectives.


127-142: Correct initialization logic per PR objectives.

The method correctly returns .empty for .strong and .empty owners (lines 136-140), as described in the PR objectives: "return .empty for .strong/.empty owners (no stream value yet); caller populates via .set()." The explanatory comment on line 138 clarifies the intended usage pattern.


144-181: Proper resource cleanup and consistent method patterns.

The deinit() method correctly handles cleanup for the .strong variant (lines 146-147) and safely no-ops for other variants. The abort(), toAnyBlob(), and done() methods follow a consistent pattern: retrieve the stream via get(), perform the operation, and call deinit() to clean up. This ensures proper resource management.


1022-1023: LGTM: Type aliases for integration.

Simple, clear type aliases that establish the integration points between ReadableStream and the Request/Response types from WebCore.

Claude Bot and others added 2 commits October 4, 2025 22:52
Addresses CodeRabbit review comments:

1. **JSBunRequest.cpp:143** - Fix WriteBarrier owner for original request body
   - Changed from `this->m_body.set(vm, clone, ...)` to `this->m_body.set(vm, this, ...)`
   - The WriteBarrier must record `this` as the mutated cell, not `clone`
   - Prevents GC from missing the strong edge and prematurely reclaiming the body

2. **Request.zig:647** - Fix tee slot assignment for Response clone
   - Changed from `tee_value[1]` to `tee_value[0]`
   - When teeing a Response body into a Request, slot[0] is for the original Response
   - Prevents Response from getting the clone's stream and losing data

3. **Response.zig:577,635** - Initialize this_jsvalue in constructor
   - Added `response.this_jsvalue = .initWeak(thisValue)` when thisValue != .zero
   - Response constructor now properly records its JS wrapper
   - Enables owner-aware code paths to recover the JS wrapper via tryGet()

These fixes ensure proper memory management and prevent stream corruption during
clone operations.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Fixes hang in async-iterator-stream tests for Response bodies with async generators.

## Root Cause

When `new Response(async generator)` is created:
1. Response constructor stores stream in GC cache (weak ref)
2. Body's `readable.Ref` is set to `.Response` (relies on GC cache)
3. RequestContext receives Response and protects the JSValue
4. BUT: Between Response creation and RequestContext using it, GC could collect the stream
5. When RequestContext calls `.get()`, it returns `null` → hangs waiting for stream

## Fix

**RequestContext.zig:1833-1836** - Upgrade Ref to `.strong` before using
- Call `.get()` to retrieve the stream
- Call `.upgrade()` to convert Ref to `.strong` (prevents GC)
- Subsequent `.get()` calls succeed with strong reference

**ReadableStream.zig:124** - Fix upgrade() bug (pre-existing)
- Changed `current.value` to `current.*`
- `Strong.init()` expects full ReadableStream struct, not just JSValue

## Results

async-iterator-stream tests:
- ✅ 45 tests pass (was 0)
- ⚠️ 41 Request body tests still timeout (different issue)

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/bun.js/webcore/ReadableStream.zig (1)

94-110: Switch bun.Output.debug calls to scoped logger.

The new debug statements should go through a scoped logger (const log = bun.Output.scoped(...)) instead of the global bun.Output.debug helper so we keep logging consistent with our Zig guidelines. Please introduce a scoped logger (e.g. near Ref) and replace the direct bun.Output.debug invocations.

+const log = bun.Output.scoped(.readable_stream_ref, false);
 ...
-            bun.Output.debug("Ref.tee: get() returned null, this={s}, owner={s}", .{ @tagName(this.*), @tagName(owner) });
+            log("Ref.tee: get() returned null, this={s}, owner={s}", .{ @tagName(this.*), @tagName(owner) });
 ...
-            bun.Output.debug("Ref.tee: stream.tee() returned null", .{});
+            log("Ref.tee: stream.tee() returned null", .{});
 ...
-            bun.Output.debug("Ref.tee: populated array with tee'd streams", .{});
+            log("Ref.tee: populated array with tee'd streams", .{});
 ...
-        bun.Output.debug("Ref.tee: calling this.set(), this was {s}, owner is {s}", .{ @tagName(this.*), @tagName(owner) });
+        log("Ref.tee: calling this.set(), this was {s}, owner is {s}", .{ @tagName(this.*), @tagName(owner) });
 ...
-        bun.Output.debug("Ref.tee: after set(), this is now {s}", .{@tagName(this.*)});
+        log("Ref.tee: after set(), this is now {s}", .{@tagName(this.*)});

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 460c31a and ff0fb1e.

📒 Files selected for processing (2)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
  • src/bun.js/webcore/ReadableStream.zig (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/api/server/RequestContext.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/api/server/RequestContext.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/api/server/RequestContext.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (2)
src/bun.js/api/server/RequestContext.zig (2)

1455-1456: LGTM: Consistent owner validation pattern.

The owner computation pattern is correctly and consistently implemented across all four call sites:

const owner = if (response.this_jsvalue.tryGet()) |jsval| 
    jsc.WebCore.ReadableStream.Ref.Owner{ .Response = jsval } 
else 
    jsc.WebCore.ReadableStream.Ref.Owner{ .empty = {} };
response.body.value.toBlobIfPossible(owner);

This aligns with the PR's fix for handling cases where no valid JSValue owner exists, preventing the "attempt to use null value" panics mentioned in the PR description.

Also applies to: 1561-1562, 1621-1622, 2179-2180


1833-1846: Retain the double-get pattern and clarify its necessity.

The first get+upgrade roots the JSValue (preventing GC) and drops the initial wrapper; the second get then creates a fresh, safe ReadableStream wrapper for use. Add an inline comment above this block explaining why both calls are required.

Likely an incorrect or invalid review comment.

Fixes remaining 41 test timeouts in async-iterator-stream tests for Request bodies.

## Root Cause

Request doesn't track `this_jsvalue` (see line 1160 TODO comment in Body.zig).
This causes:
1. Request constructor stores stream in GC cache with thisValue
2. Body's `readable.Ref` is set to `.Request`
3. When `getText()`/`json()`/etc is called, owner is always `.empty` (no JSValue)
4. `.get(.empty)` returns null → timeout waiting for stream

## Fix

**Request.zig:800-803** - Upgrade Request body Ref to `.strong` in constructor
- After setting GC cache, retrieve the stream
- Call `.upgrade()` to convert Ref from `.Request` (unusable) to `.strong`
- Now `getText()/etc` can retrieve stream via strong ref

## Results

async-iterator-stream tests:
- ✅ 85/86 tests pass (was 45/86)
- 1 test has unrelated chunk length expectation issue

All Request body method tests pass:
- Request.text() ✅
- Request.json() ✅
- Request.blob() ✅
- Request.arrayBuffer() ✅
- Request.bytes() ✅

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff0fb1e and 60322d7.

📒 Files selected for processing (1)
  • src/bun.js/webcore/Request.zig (14 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (5)
src/bun.js/webcore/Request.zig (5)

71-72: LGTM: Signature correctly extended for stream ownership tracking.

The signature extension to include this_value and tee parameters properly supports the ReadableStream ownership model described in the PR objectives.


783-807: LGTM: Constructor correctly implements strong ref upgrade for stream ownership.

The post-construction logic (lines 795-804) properly addresses the core issue from this PR. Since Request doesn't store thisValue as a field, subsequent operations like getText() would attempt to retrieve the stream without a valid owner, causing panics. The upgrade to strong ref ensures the stream remains accessible.


203-203: LGTM: Correct owner parameter for size calculations.

Using .empty owner for size() calls in formatting contexts is appropriate, as these calls don't perform stream operations and just need the size value.

Also applies to: 242-242


250-250: LGTM: Strong ref access appropriate for formatting.

Using .strong to retrieve the readable stream in writeFormat ensures the stream can be accessed for display purposes, even when there's no JS owner context. This aligns with the PR's approach of using strong refs when a valid owner isn't available.


645-648: LGTM: Tee slot assignment correctly addressed from previous review.

The code correctly assigns tee_value[0] (the original branch) back to the Response's GC cache, as noted in the previous review comment that was marked as addressed. This ensures the Response retains the correct stream slot while tee_value[1] goes to the new Request.

Comment thread src/bun.js/webcore/Request.zig
- Add  field to Body.Value.Locked.PendingValue
  as fallback strong ref when no owner JSValue exists (e.g., Request)
- Add  helper to check both GC cache and fallback strong ref
- Update  to use  instead of direct
- Update  to use  and manual stream.tee()
  instead of Ref.tee() to support fallback strong ref
- Remove unnecessary GC cache setting in Request constructor
- Properly cleanup fallback strong ref in deinit()

This architecture keeps Request/Response holding weak refs (via GC cache
or empty) while consumers (RequestContext, .text() operations) hold
strong refs. Request bodies work without needing this_jsvalue field by
using the fallback strong ref storage.

Result: 85/86 async-iterator-stream tests passing (1 flaky test)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 60322d7 and c470ef9.

📒 Files selected for processing (3)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
  • src/bun.js/webcore/Body.zig (42 hunks)
  • src/bun.js/webcore/Request.zig (14 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig

Comment thread src/bun.js/webcore/Body.zig Outdated
Claude Bot added 2 commits October 5, 2025 01:33
Instead of fallback strong ref architecture, give Request the same
this_jsvalue field that Response has. This allows Request to use the
standard GC cache mechanism for storing/retrieving body streams.

- Add `this_jsvalue: jsc.JSRef` field to Request struct
- Initialize this_jsvalue in Request constructor
- Use setValue() to properly set GC cache and update Ref type
- Update BodyMixin to use this_jsvalue for both Request and Response
- Remove fallback strong ref mechanism (getStream, stream field, etc)

This is the correct architecture: containers (Request/Response) hold
WEAK refs via GC cache, consumers hold STRONG refs.

Result: 85/86 async-iterator-stream tests passing (1 flaky test passes
in isolation, fails when run with full suite - likely pre-existing)
All 86 async-iterator-stream tests now passing!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c470ef9 and 2c847c1.

📒 Files selected for processing (4)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
  • src/bun.js/webcore/Body.zig (42 hunks)
  • src/bun.js/webcore/ReadableStream.zig (2 hunks)
  • src/bun.js/webcore/Request.zig (15 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/Body.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/Body.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/ReadableStream.zig
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/Body.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig

Comment thread src/bun.js/webcore/Request.zig
Properly cleanup the JSRef when Request is finalized to prevent
memory leaks.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/bun.js/webcore/Request.zig (1)

177-181: Store the wrapper JSValue in this_jsvalue.

This issue was flagged in a previous review but remains unaddressed. The toJS method creates a JS wrapper but never stores it in this.this_jsvalue. Callers like Request.clone() and body getters depend on that weak ref when synthesizing the owner for ReadableStream.Ref. Without storing it, the cloned request's Body.Value loses the owner context, and later getBody() calls see an .empty owner, failing to retrieve the tee'd branch from the GC cache.

Apply this diff to persist the wrapper:

 pub fn toJS(this: *Request, globalObject: *JSGlobalObject) JSValue {
     this.calculateEstimatedByteSize();
     const value = js.toJSUnchecked(globalObject, this);
+    if (value != .zero) {
+        this.this_jsvalue.finalize();
+        this.this_jsvalue = .initWeak(value);
+    }
     return value;
 }
🧹 Nitpick comments (1)
src/bun.js/webcore/Request.zig (1)

204-204: Minor: Use consistent syntax for empty owner parameter.

Line 204 uses .{ .empty = {} } while Line 243 uses .empty. Both are valid, but using consistent syntax improves readability.

Consider standardizing on one form, e.g.:

-try writer.print("{s} ({}) {{\n", .{ class_label, bun.fmt.size(this.body.value.size(.{ .empty = {} }), .{}) });
+try writer.print("{s} ({}) {{\n", .{ class_label, bun.fmt.size(this.body.value.size(.empty), .{}) });

Also applies to: 243-243

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2c847c1 and af6fa5b.

📒 Files selected for processing (1)
  • src/bun.js/webcore/Request.zig (16 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig
🔇 Additional comments (9)
src/bun.js/webcore/Request.zig (9)

18-18: LGTM!

The this_jsvalue field addition enables weak reference tracking of the JS wrapper for proper GC cache support.


72-73: LGTM!

The signature correctly accepts this_value and tee parameters to support tee'd ReadableStream propagation through cloning.


251-251: LGTM!

Using .strong ref type here ensures the ReadableStream remains accessible for formatting/inspection purposes, even when there's no valid owner context. This aligns with the PR's fix for handling streams without valid owners.


357-357: LGTM!

Properly finalizes the this_jsvalue JSRef during Request cleanup.


528-528: LGTM!

The signature update enables tee'd ReadableStream propagation during Request construction.


588-588: LGTM!

The tee handling correctly:

  • Threads the tee parameter through cloning (line 588)
  • Uses the valid JSValue as owner when cloning from Request (line 616)
  • Stores the original branch (tee[0]) back to the Response GC cache when cloning from Response (lines 647-650), as correctly fixed per past review
  • Passes the clone branch (tee[1]) when constructing the new Request body (line 664)

Also applies to: 616-620, 647-654, 664-665


785-807: LGTM!

The constructor correctly implements the fix described in the PR objectives:

  • Checks owner validity before initializing the weak ref (line 798)
  • Only populates the GC cache when the owner is valid (lines 801-803)
  • Uses setValue to properly store the tee'd stream with the Request owner

This prevents the "attempt to use null value" panics that occurred when owner was .zero or .empty.


815-835: LGTM!

The doClone method correctly stores both tee'd stream branches in their respective GC caches:

  • tee[0] for the original Request (line 826)
  • tee[1] for the cloned Request (line 830)

The concern about upgrading to a strong ref was addressed in a previous commit.


875-875: LGTM!

The remaining changes correctly propagate this_value and readable_stream_tee through the clone methods:

  • Line 875 updates syntax for consistency with other owner parameter usage
  • cloneInto and clone methods thread the parameters through to body cloning operations
  • All parameter passing is consistent with the tee propagation pattern

Also applies to: 942-980

- Store the wrapper JSValue in this_jsvalue during toJS() so cloned
  requests and body getters can retrieve the owner from GC cache
- Finalize old this_jsvalue before storing new one to prevent leaks
- Standardize empty owner syntax to use .empty consistently

This fixes the issue where Request.clone() would lose owner context
because toJS() creates the wrapper but never stores it for later
retrieval.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/bun.js/webcore/Request.zig (1)

255-255: Use .empty owner for read-only stream access in writeFormat
At Request.zig:255, switch the .get call to use .empty instead of .strong for display‐only formatting—this avoids an unnecessary strong reference and aligns with fetch.zig’s usage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af6fa5b and 26cbce6.

📒 Files selected for processing (1)
  • src/bun.js/webcore/Request.zig (16 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (17)
src/bun.js/webcore/Request.zig (17)

18-18: LGTM: this_jsvalue field addition.

The this_jsvalue field enables proper GC cache tracking for Request instances, which is essential for the ReadableStream.Ref system fixes described in the PR objectives.


72-73: LGTM: Export function signature updated correctly.

The updated signature properly threads this_value and tee parameters to support the new tee stream propagation model.


177-185: LGTM: Wrapper JSValue now properly persisted.

This correctly implements the fix from the previous review. The wrapper is now stored in this_jsvalue so that callers like Request.clone() and body getter can rely on the weak ref for GC-dependent owner lookups.


208-208: LGTM: Owner parameter added for size query.

Using .empty is appropriate here since this is a display/formatting operation that doesn't require a JS wrapper reference.


247-247: LGTM: Consistent owner parameter usage.

Correctly uses .empty for the size query in this display context.


361-361: LGTM: Proper cleanup of JS reference.

Correctly finalizes the weak reference before other cleanup, following the pattern established in the codebase.


532-532: LGTM: Signature updated for tee stream propagation.

The added readable_stream_tee parameter enables proper handling of teed streams during Request construction.


592-592: LGTM: Owner and tee parameters properly threaded.

Correctly passes value (the source Request wrapper) and readable_stream_tee to cloneInto for proper stream handling.


620-620: LGTM: Body clone with correct owner.

Properly specifies the source Request wrapper as owner and passes the tee parameter for stream splitting.


668-668: LGTM: Tee stream captured for new Request body.

Correctly passes &tee[1] to capture the cloned branch of any teed ReadableStream.


825-836: Verify: Is upgrade() needed after GC cache set?

The past review comment on this section (marked as addressed in commit c470ef9) mentioned that doClone should upgrade the cloned stream to a strong ref after setting the GC cache, similar to the constructor pattern. The current code sets both tee branches in the GC cache but doesn't call upgrade().

Please verify whether:

  1. The js.gc.body.set() calls now handle ownership internally without needing explicit upgrade(), or
  2. The explicit upgrade() pattern is still needed for the cloned Request after line 834.

Based on past review at lines 825-835.


879-879: LGTM: Empty owner for read-only stream access.

Correctly uses .{ .empty = {} } for inspecting the stream's content type without requiring a JS wrapper reference.


952-953: LGTM: Signature updated for owner and tee propagation.

The added parameters enable proper owner tracking and tee stream handling during Request cloning.


958-958: LGTM: Body clone with correct owner in cloneInto.

Properly specifies the source Request wrapper as owner and threads the tee parameter.


979-982: LGTM: Clone method signature completed.

The updated signature and parameter forwarding complete the tee stream propagation pattern for Request cloning.


651-654: Verify clone owner for Response body during tee
Ensure the cloned body uses the Response wrapper (value) as its owner instead of .empty when tee-ing and updating tee_value[0], to maintain correct ownership semantics.


789-810: No explicit upgrade required: ReadableStream.setValue(owner: .Request, …) populates the GC cache and updates the Ref variant; an explicit upgrade() isn’t needed here, matching the Response.zig constructor pattern.

Use proper Request owner from this_jsvalue when getting readable stream
to extract content_type, instead of hardcoded .empty owner.

This fixes the FormData Content-Type header becoming null after
accessing request.body. The issue was that after my changes, Locked
bodies have Ref type .Request, but ensureFetchHeaders was trying to
get the stream with .empty owner, causing a mismatch and null return.

FormData tests now: 108 pass, 1 fail (timeout - pre-existing on main)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 26cbce6 and 53c301d.

📒 Files selected for processing (1)
  • src/bun.js/webcore/Request.zig (16 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig

Comment thread src/bun.js/webcore/Request.zig
…treams

Addresses CodeRabbit review comment: doClone must call setValue on both
the original and cloned Request's Locked.readable Ref after setting the
GC cache, mirroring the constructor pattern. This ensures the Ref is
properly initialized with the tee'd stream value, making the body stream
accessible via the owner-aware get() path.
@robobun

robobun commented Oct 5, 2025

Copy link
Copy Markdown
Collaborator Author

✅ All CodeRabbit review comments have been addressed:

  1. JSBunRequest.cpp:148 - WriteBarrier owner fixed to use this instead of clone (commit 460c31a)
  2. Request.zig:654 - Tee slot assignment fixed to use tee_value[0] for Response (commit 460c31a)
  3. Request.zig:185 - Store wrapper JSValue in toJS() via this.this_jsvalue (commit 26cbce6)
  4. Request.zig:835-836 - Add setValue() calls in doClone for both original and cloned Request Refs (commit 100b7fc)

The doClone method now properly initializes the Locked.readable Ref with tee'd stream values by calling setValue() after setting the GC cache, mirroring the constructor pattern at lines 805-807.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/bun.js/webcore/Request.zig (1)

532-532: Consider renaming the dual-purpose parameter for clarity.

The readable_stream_tee parameter serves two distinct purposes:

  1. Returning tee'd stream branches [0] and [1] when cloning a Request/Response
  2. Returning the extracted stream JSValue in slot [1] when parsing a plain body object

While the current implementation works correctly, the parameter name suggests it's only for tee operations, which can be misleading. Consider renaming it to something more generic like readable_stream_out or adding a comment explaining the dual usage.

Example documentation addition:

+// readable_stream_tee: output parameter for ReadableStream JSValues
+// - When cloning: [0] = original branch, [1] = clone branch  
+// - When extracting: [1] = extracted stream value
 pub fn constructInto(globalThis: *jsc.JSGlobalObject, arguments: []const jsc.JSValue, readable_stream_tee: ?*[2]jsc.JSValue) bun.JSError!Request {

Also applies to: 668-668, 796-796

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 53c301d and 100b7fc.

📒 Files selected for processing (1)
  • src/bun.js/webcore/Request.zig (16 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/Request.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/Request.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/Request.zig
🧠 Learnings (1)
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/webcore/Request.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (5)
src/bun.js/webcore/Request.zig (5)

789-811: LGTM: Constructor properly initializes owner tracking and tee'd streams.

The implementation correctly:

  1. Accepts thisValue to track the JS wrapper
  2. Initializes the tee array for stream splitting
  3. Stores thisValue as a weak reference in this_jsvalue for later owner lookups
  4. Associates the tee'd stream branch with the new Request via setValue

The conditional check if (thisValue != .zero) appropriately handles edge cases where the wrapper might not be immediately available.


819-841: LGTM: doClone correctly assigns tee branches and updates both Requests.

The implementation properly:

  1. Creates a tee array to capture the split stream branches
  2. Assigns tee[0] to the original Request (this) via GC cache and setValue
  3. Assigns tee[1] to the cloned Request via GC cache and setValue

Each Request now maintains the correct stream branch with its owner properly tracked.


651-654: LGTM: Response tee branch assignment is correct.

When constructing a Request from a Response, the code properly:

  1. Clones the Response body (tee'ing if it's a stream)
  2. Assigns tee_value[0] to the Response's GC cache (keeping the original branch)
  3. Implicitly reserves tee_value[1] for the new Request (assigned later in constructor)

This correctly implements the tee semantics where the original Response retains branch [0] and the new Request gets branch [1].


177-185: LGTM: toJS properly maintains wrapper tracking.

The implementation correctly:

  1. Finalizes any existing weak reference to prevent leaks
  2. Stores the new JS wrapper as a weak reference in this_jsvalue
  3. Returns the wrapper value

This ensures subsequent operations can retrieve the owner context via this_jsvalue.tryGet().


879-890: LGTM: Owner retrieval with appropriate fallback.

The code correctly:

  1. Attempts to retrieve the owner from this_jsvalue.tryGet()
  2. Falls back to .empty if the wrapper is not available
  3. Uses the owner to safely access the stream content_type

The .empty fallback is appropriate for cases where the Request hasn't been fully initialized yet or the wrapper was never set.

@robobun

robobun commented Oct 5, 2025

Copy link
Copy Markdown
Collaborator Author

Update on CodeRabbit comment about setValue in doClone

After implementing the suggested setValue calls in doClone (commit 100b7fc), this caused 120+ test failures in body-stream.test.ts with req.body becoming null.

Root cause: setValue calls this.deinit() which destroys the existing Ref. This works fine in the constructor (line 806) because the Request is newly created and the Ref hasn't been initialized yet. But in doClone, both the original and cloned Request already have initialized Refs from the clone operation, so calling setValue destroys them.

Solution: Reverted in commit 07b3a65. The GC cache via js.gc.body.set() is sufficient - the Ref system retrieves streams from the GC cache in Ref.get() based on owner type (.Request or .Response), so no explicit setValue is needed after cloning.

The constructor needs setValue because it's initializing a brand new Request, but doClone works correctly with just the GC cache updates.

Claude Bot added 2 commits October 5, 2025 06:08
When a request ends with a pending read on the request body stream,
the stream must be aborted to reject the pending read promise.

This mirrors the existing response body abort logic but uses the
request's this_jsvalue to get the proper owner for the Ref system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07b3a65 and a6745df.

📒 Files selected for processing (1)
  • src/bun.js/api/server/RequestContext.zig (14 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/api/server/RequestContext.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/api/server/RequestContext.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/api/server/RequestContext.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format

Comment thread src/bun.js/api/server/RequestContext.zig Outdated
Address CodeRabbit review comments to fix memory safety issues:

1. Move request body abort before deref() in RequestContext.finalize()
   - The abort logic was unreachable because it ran after deref() cleared the weak ref
   - Now aborts locked request body streams before releasing the weak reference

2. Preserve tee'd stream for owner-less clones in Body.tee()
   - When readable_stream_tee is supplied but owner is .empty, keep a strong ref
   - Prevents GC from reclaiming the tee branch before Request/Response gains owner
   - Fixes issue where cloned bodies appeared empty or disturbed

These changes ensure proper stream lifecycle management and prevent premature
garbage collection of tee'd ReadableStream branches.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/bun.js/webcore/Body.zig (1)

1053-1066: Critical: Tee'd streams lost for owner-less clones.

When readable_stream_tee is non-null and the owner is .empty or .strong (lines 1061-1062), both tee branches are lost:

  1. Original branch: Line 1047 calls .set() which is a no-op for .empty/.strong owners, and locked.readable remains .empty (from not being set in lines 502-513), so the first branch is not retained
  2. Clone branch: Line 1061 returns .empty, so result.@"1" is not retained

This causes both branches to be GC'd, leaving the clone without a tracked stream and potentially crashing when the body is read.

Apply this diff to retain both branches via strong refs:

                 const tee_result = (try locked.readable.tee(owner, globalThis, readable_stream_tee)) orelse return Value{ .Used = {} };
 
                 if (readable_stream_tee != null) {
+                    // For owner-less or strong-ref scenarios, must retain the clone branch
+                    const clone_readable: jsc.WebCore.ReadableStream.Ref = switch (owner) {
+                        .Response => .Response,
+                        .Request => .Request,
+                        else => .{ .strong = .init(tee_result.@"1", globalThis) },
+                    };
+
                     return .{
                         .Locked = .{
-                            .readable = switch (owner) {
-                                .Response => .Response,
-                                .Request => .Request,
-                                else => .empty,
-                            },
+                            .readable = clone_readable,
                             .global = globalThis,
                         },
                     };
                 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a6745df and a68d917.

📒 Files selected for processing (2)
  • src/bun.js/api/server/RequestContext.zig (15 hunks)
  • src/bun.js/webcore/Body.zig (42 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/server/RequestContext.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/api/server/RequestContext.zig
  • src/bun.js/webcore/Body.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (9)
src/bun.js/api/server/RequestContext.zig (3)

630-631: Verify abort logic executes correctly.

The captured response_jsvalue (line 630) and the new request body abort block (lines 653-663) execute before deref() is called on line 665, addressing the prior concern about the abort hook never firing. Confirm that:

  1. The request body abort (lines 656-662) uses request.this_jsvalue.tryGet() to derive the owner, falling back to .empty when no JSValue exists
  2. The response body abort (line 696) uses the captured response_jsvalue

Both streams are now properly aborted while their owning Request/Response objects are still reachable.

Also applies to: 653-663


1467-1469: LGTM: Consistent owner derivation pattern.

The owner derivation logic correctly checks response.this_jsvalue.tryGet() and falls back to .empty when no valid JSValue exists. This pattern is applied consistently across HEAD response rendering, fulfilled promise handling, and error paths, ensuring proper ReadableStream lifecycle management.

Also applies to: 1574-1575, 1634-1635, 2186-2187


1230-1230: LGTM: Proper ReadableStream.Ref lifecycle management.

The ReadableStream.Ref initialization and cleanup integrates correctly with the owner-aware model:

  1. Strong refs created when storing streams in response bodies (line 1230, 2517)
  2. Owner-aware cleanup using tryGet() to derive owner before calling done() or falling back to deinit() (lines 1679-1683, 1733-1737)
  3. Proper ref swapping in the scoped block (lines 1847-1852) with old ref cleanup, followed by error-path cleanup (line 1860)
  4. Byte stream ref management for piping (lines 1904-1907)

The scoped block at lines 1847-1852 correctly deinitializes the old ref before checking for lock status, and the error path at line 1860 properly cleans up the new ref if the stream is already locked.

Also applies to: 1679-1683, 1733-1737, 1846-1852, 1860-1860, 1904-1907, 2517-2517

src/bun.js/webcore/Body.zig (6)

58-58: LGTM: Core type change enables per-owner refs.

The change from ReadableStream.Strong = .{} to ReadableStream.Ref = .empty is the foundational modification that enables the per-owner ReadableStream reference model. The .empty initialization indicates no stream is currently tracked, consistent with the new Ref semantics.


443-456: LGTM: Correct owner-aware stream initialization for blob conversion.

The blob-to-ReadableStream conversion properly handles all owner scenarios:

  1. Request/Response with valid JSValue (lines 444-445): Uses type-based ref (.Request/.Response) and populates GC cache via .set() (lines 453-454)
  2. Request/Response with .zero JSValue: Falls back to .strong ref to keep stream alive until a real owner exists
  3. .strong/.empty owners (line 446): Uses strong ref; no GC cache population since there's no owner to key on

This prevents premature GC of the stream while ensuring proper lifecycle management once an owner is available.


627-633: LGTM: Deferred stream setup pattern.

The new fromJSWithReadableStreamValue function supports deferred ReadableStream setup by returning a .Locked value with .empty readable when readable_stream_value is provided (lines 627-633). The caller receives the stream JSValue via the pointer parameter and is responsible for populating the readable ref later (either via GC cache for Request/Response owners or by upgrading to a strong ref).

This pattern enables Request/Response constructors to defer stream initialization until after the wrapper JSValue is available.


1132-1140: LGTM: Type-safe owner derivation helper.

The getOwner inline helper provides compile-time type-safe owner derivation for the Mixin methods. The @compileError for unsupported types ensures the Mixin is only used with Request or Response types, preventing runtime errors from incorrect owner types.


1142-1378: LGTM: Consistent owner-aware Mixin methods.

All Mixin methods correctly derive the owner and propagate it through Body.Value operations:

  1. JS-facing methods (getText, getJSON, getArrayBuffer, getBytes, getFormData, getBlob) use getOwner(callframe.this()) to derive owner from the current call frame
  2. Internal methods (getBody, getBodyUsed, getBlobWithThisValue, getBlobWithoutCallFrame) use this.this_jsvalue.tryGet() with .zero check and .empty fallback for cases where the JSValue isn't available yet

The pattern ensures proper ReadableStream lifecycle management while handling both JavaScript-invoked and internally-invoked code paths.


1436-1436: LGTM: Appropriate use of .empty owner in ValueBufferer.

ValueBufferer correctly uses .empty owner throughout (lines 1436, 1629, 1677, 1696) since it's an internal utility not associated with a specific Request/Response. The stream lifecycle transfer at lines 1635-1637 properly moves the stream to ValueBufferer's strong ref while marking the original value as Used, preventing double-ownership.

Also applies to: 1629-1629, 1635-1637, 1677-1677, 1696-1696

Comment thread src/bun.js/webcore/Body.zig
Claude Bot and others added 2 commits October 5, 2025 12:14
When converting a drain result to a ReadableStream, the code was calling
.set() to populate the GC cache but never updating locked.readable to the
matching owner variant (.Request or .Response). This left it as .empty,
breaking subsequent .get(owner, globalThis) calls.

Now matches the blob conversion pattern by:
1. Setting locked.readable to the correct owner variant (or strong ref fallback)
2. Only calling .set() when owner has a valid JSValue

This ensures the ref type matches the GC cache key for proper stream retrieval.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/bun.js/webcore/fetch.zig (2)

187-204: Make the strong-move explicit and drop redundant check

Logic is correct (owner-aware fallback and guard against Used/Disturbed). Two small cleanups:

  • Make the “take strong and clear local” move explicit to avoid aliasing confusion.
  • The second body_value == .Locked check is redundant within the enclosing branch.

Suggested diff:

-                if (body_value.Locked.readable == .strong) {
-                    // just grab the ref
-
-                    defer body_value.Locked.readable = .empty;
-                    return FetchTasklet.HTTPRequestBody{ .ReadableStream = body_value.Locked.readable.strong };
-                }
+                if (body_value.Locked.readable == .strong) {
+                    // move out the strong ref
+                    var prev = body_value.Locked.readable;
+                    body_value.Locked.readable = .{ .empty = {} };
+                    return FetchTasklet.HTTPRequestBody{ .ReadableStream = prev.strong };
+                }
@@
-                if (!readable.isEmptyOrUndefinedOrNull() and body_value == .Locked and body_value.Locked.readable.has(.empty, globalThis)) {
+                if (!readable.isEmptyOrUndefinedOrNull() and body_value.Locked.readable.has(.empty, globalThis)) {

2113-2124: Request.body owner-threading LGTM; consider small DRY refactor

  • Correctly threads { .Request = first_arg } into isDisturbed, has, get, and toReadableStream.
  • Switching fromJS to use globalThis is consistent with other call sites.

Minor: assign the owner once to avoid repetition.

-            if (req.body.value == .Used or (req.body.value == .Locked and (req.body.value.Locked.action != .none or req.body.value.Locked.isDisturbed(.{ .Request = first_arg }, globalThis)))) {
+            const owner = .{ .Request = first_arg };
+            if (req.body.value == .Used or (req.body.value == .Locked and (req.body.value.Locked.action != .none or req.body.value.Locked.isDisturbed(owner, globalThis)))) {
@@
-                if (req.body.value.Locked.readable.has(.{ .Request = first_arg }, globalThis)) {
-                    break :extract_body FetchTasklet.HTTPRequestBody{ .ReadableStream = jsc.WebCore.ReadableStream.Strong.init(req.body.value.Locked.readable.get(.{ .Request = first_arg }, globalThis).?, globalThis) };
+                if (req.body.value.Locked.readable.has(owner, globalThis)) {
+                    break :extract_body FetchTasklet.HTTPRequestBody{ .ReadableStream = jsc.WebCore.ReadableStream.Strong.init(req.body.value.Locked.readable.get(owner, globalThis).?, globalThis) };
                 }
-                const readable = try req.body.value.toReadableStream(.{ .Request = first_arg }, globalThis);
-                if (!readable.isEmptyOrUndefinedOrNull() and req.body.value == .Locked and req.body.value.Locked.readable.has(.{ .Request = first_arg }, globalThis)) {
-                    break :extract_body FetchTasklet.HTTPRequestBody{ .ReadableStream = jsc.WebCore.ReadableStream.Strong.init(req.body.value.Locked.readable.get(.{ .Request = first_arg }, globalThis).?, globalThis) };
+                const readable = try req.body.value.toReadableStream(owner, globalThis);
+                if (!readable.isEmptyOrUndefinedOrNull() and req.body.value == .Locked and req.body.value.Locked.readable.has(owner, globalThis)) {
+                    break :extract_body FetchTasklet.HTTPRequestBody{ .ReadableStream = jsc.WebCore.ReadableStream.Strong.init(req.body.value.Locked.readable.get(owner, globalThis).?, globalThis) };
                 }

Also applies to: 2131-2134

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d56fd9a and 35338e6.

📒 Files selected for processing (1)
  • src/bun.js/webcore/fetch.zig (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: In Zig source files, place all @import statements at the bottom of the file
Use @import("bun") instead of @import("root").bun when importing Bun in Zig

Files:

  • src/bun.js/webcore/fetch.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/webcore/fetch.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/webcore/fetch.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (1)
src/bun.js/webcore/fetch.zig (1)

456-475: Owner-aware Response readable retrieval looks correct

Using response.this_jsvalue.tryGet() as owner to fetch the readable, and falling back to .empty is aligned with the new Ref model. Clearing Locked.readable to explicit empty and deinit’ing the previous ref avoids leaks/dangling refs. LGTM.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Fixed in #23313

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants