Skip to content

perf(bridge/core): apply daily incremental optimizations and safety guardrails#156

Open
repyh wants to merge 1 commit into
mainfrom
perf-audit-bridge-core-655556913855000679
Open

perf(bridge/core): apply daily incremental optimizations and safety guardrails#156
repyh wants to merge 1 commit into
mainfrom
perf-audit-bridge-core-655556913855000679

Conversation

@repyh
Copy link
Copy Markdown
Owner

@repyh repyh commented May 17, 2026

Daily performance audit for the bridge/core and eventloop module. Focusing on cache locality, strict type formatting, and slice isolation for shared memory bindings between Go and JS. Fixes multiple safety edge cases while providing direct performance uplifts.


PR created automatically by Jules for task 655556913855000679 started by @repyh

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of binary data handling with fallback mechanisms for environments with limited JavaScript ArrayBuffer/Uint8Array support.
    • Fixed promise settlement to ensure callbacks execute exactly once per promise.
  • Performance

    • Optimized map and slice binding operations for better efficiency in data conversion.

Review Change Stack

…uardrails

- **eventloop**: Reordered `EventLoop` struct for better cache locality and added `sync.Once` wrapper around Promise resolution loop scheduling to prevent multiple `wg.Done()` calls.
- **arraybuffer**: Enforced memory isolation in `ToArrayBuffer` by copying the byte slice before exposing it to the JS runtime to prevent memory mutation. Introduced strict fallback checks in `MapSharedBuffer` and a new `ToUint8Array` utility.
- **reflection**: Implemented `MapRange` in `bindMap` to avoid `MapKeys` slice allocations and used `strconv` functions to bypass interface boxing string conversion for numeric keys. Added a fast-path in `bindSlice` to securely process `[]byte` mapping.

Co-authored-by: repyh <63894915+repyh@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR improves type conversion safety, optimizes reflection bindings, and fixes event loop promise synchronization. Array buffer operations now explicitly copy input bytes and defensively handle Uint8Array availability. Reflection binding gains a fast path for byte slices and more efficient map key formatting. Event loop promise settlement is guarded by sync.Once to ensure single execution.

Changes

Type Conversion and Reflection Optimizations

Layer / File(s) Summary
Array Buffer Safety and Typed Array Handling
bridge/core/arraybuffer.go
ToArrayBuffer explicitly copies input bytes before creation. ToUint8Array checks for constructor availability and view success, falling back to ArrayBuffer on failure. MapSharedBuffer conditionally creates Uint8Array views only when safe, otherwise exposes the raw ArrayBuffer.
Reflection Binding Optimizations
bridge/core/reflection.go
bindSlice gains a fast path for []byte that directly converts to Uint8Array via ToUint8Array. bindMap uses MapRange for allocation-free iteration and formats integer/uint map keys efficiently with strconv.FormatInt/FormatUint, falling back to fmt.Sprint for other types. Adds strconv import.

Event Loop Promise Synchronization

Layer / File(s) Summary
Promise Settlement Synchronization
eventloop/eventloop.go
CreatePromise resolve and reject paths now share a sync.Once guard ensuring promise settlement and wg.Done() execute exactly once per promise. EventLoop struct field order adjusted to place synchronization fields (mu, running, autoStop) before wg.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Bytes copy safe, arrays dance with care,
Maps iterate swift, keys formatted fair,
Promises settle once—no more double play,
The bridge flows stronger now, hooray hooray! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: performance optimizations (explicit copying in ToArrayBuffer, efficient key conversion with strconv, cache-locality reordering) and safety improvements (sync.Once for promise resolution, fallback checks in MapSharedBuffer, memory isolation in shared buffer bindings).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-audit-bridge-core-655556913855000679

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
bridge/core/arraybuffer.go (1)

50-63: ⚡ Quick win

Update MapSharedBuffer docs to reflect ArrayBuffer fallback.

Behavior now conditionally exposes ArrayBuffer, but Line 41-42 still says it is exposed as Uint8Array. Please align the comment with runtime behavior.

📝 Suggested doc update
-// as a Uint8Array in JavaScript. This is commonly used for inter-worker
-// communication and zero-copy data sharing.
+// as a Uint8Array when available. In restricted runtimes where Uint8Array
+// is unavailable, it falls back to exposing the ArrayBuffer directly.
+// This is commonly used for inter-worker communication and zero-copy data sharing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bridge/core/arraybuffer.go` around lines 50 - 63, Update the
documentation/comment for MapSharedBuffer to match the implementation: state
that MapSharedBuffer tries to construct a Uint8Array view (using ctor :=
vm.Get("Uint8Array") and vm.New(ctor.ToObject(vm), buf)) and, if that fails,
falls back to exposing the raw ArrayBuffer (view = buf) under the given name;
modify the comment near the MapSharedBuffer function to clearly mention the
conditional behavior and fallback to ArrayBuffer instead of always claiming it
is exposed as Uint8Array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eventloop/eventloop.go`:
- Around line 126-146: CreatePromise currently calls el.wg.Add(1) after Start's
auto-stop goroutine may have already observed zero, violating sync.WaitGroup
semantics and causing a panic; fix by ensuring the promise's liveness is
registered before the auto-stop waiter can return: either (1) guard
CreatePromise with the running state (check the Start/Stop-running flag in Start
and refuse/fail fast in CreatePromise), or (2) move the el.wg.Add(1)
registration to a point that happens before the auto-stop goroutine is started
(or start the auto-stop goroutine only after an initial reservation), or (3)
disable auto-stop when asynchronous promises are expected; reference
CreatePromise, Start, el.wg.Add(1), and the auto-stop goroutine to locate the
changes.

---

Nitpick comments:
In `@bridge/core/arraybuffer.go`:
- Around line 50-63: Update the documentation/comment for MapSharedBuffer to
match the implementation: state that MapSharedBuffer tries to construct a
Uint8Array view (using ctor := vm.Get("Uint8Array") and
vm.New(ctor.ToObject(vm), buf)) and, if that fails, falls back to exposing the
raw ArrayBuffer (view = buf) under the given name; modify the comment near the
MapSharedBuffer function to clearly mention the conditional behavior and
fallback to ArrayBuffer instead of always claiming it is exposed as Uint8Array.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c81f3529-27ff-4698-95dc-e110d0eda1d7

📥 Commits

Reviewing files that changed from the base of the PR and between b85f440 and 485fecf.

📒 Files selected for processing (3)
  • bridge/core/arraybuffer.go
  • bridge/core/reflection.go
  • eventloop/eventloop.go

Comment thread eventloop/eventloop.go
Comment on lines 126 to +146
// Keep the loop alive until the promise is settled
el.wg.Add(1)

// @optimized: Use sync.Once to ensure loop scheduling and wg.Done() are called exactly once per promise.
var once sync.Once

resolve = func(v interface{}) {
el.RunOnLoop(func() {
_ = res(v)
el.wg.Done()
once.Do(func() {
el.RunOnLoop(func() {
_ = res(v)
el.wg.Done()
})
})
}

reject = func(v interface{}) {
el.RunOnLoop(func() {
_ = rej(v)
el.wg.Done()
once.Do(func() {
el.RunOnLoop(func() {
_ = rej(v)
el.wg.Done()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

According to the Go sync.WaitGroupdocumentation, is it safe to callAdd(1)after another goroutine has already calledWait() and observed the counter as zero?

💡 Result:

No, it is not safe to call Add(1) after a WaitGroup's counter has reached zero and a Wait call has returned [1][2]. According to the official Go documentation for sync.WaitGroup, calls with a positive delta that occur when the counter is zero must happen before a Wait call [1][2]. If you intend to reuse a WaitGroup to wait for several independent sets of events, any new Add calls must occur only after all previous Wait calls have returned [1][2]. Attempting to call Add after Wait has already unblocked (because the counter reached zero) is considered a misuse of the WaitGroup and can lead to panics or race conditions [1][3]. The implementation of Add includes explicit checks to detect such misuse, and it will panic if it detects that Add is being called concurrently with Wait or after the counter has been reset by a completed Wait operation [1].

Citations:


🏁 Script executed:

cd eventloop 2>/dev/null || cd . && find . -name "eventloop.go" -type f

Repository: repyh/typego

Length of output: 71


🏁 Script executed:

wc -l ./eventloop.go && echo "---" && cat -n ./eventloop.go

Repository: repyh/typego

Length of output: 102


🏁 Script executed:

find . -name "eventloop.go" -type f

Repository: repyh/typego

Length of output: 81


🏁 Script executed:

wc -l ./eventloop/eventloop.go && echo "---" && cat -n ./eventloop/eventloop.go

Repository: repyh/typego

Length of output: 3779


CreatePromise violates Go sync.WaitGroup semantics and will panic.

If Start() is called with autoStop enabled (the default), the auto-stop goroutine at line 60–63 will immediately observe the WaitGroup counter as zero and call Wait(), returning instantly. Any subsequent call to CreatePromise() will then call Add(1) at line 127 on a WaitGroup that has already returned from Wait() with counter zero. According to Go's sync.WaitGroup documentation, this is explicitly forbidden and will panic at runtime.

To fix this, either:

  • Prevent CreatePromise() from being called after Start() (e.g., check running flag and fail fast), or
  • Register the promise's liveness before the auto-stop waiter can observe zero (e.g., call Add(1) before launching the auto-stop goroutine), or
  • Disable auto-stop when promises may be created asynchronously.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventloop/eventloop.go` around lines 126 - 146, CreatePromise currently calls
el.wg.Add(1) after Start's auto-stop goroutine may have already observed zero,
violating sync.WaitGroup semantics and causing a panic; fix by ensuring the
promise's liveness is registered before the auto-stop waiter can return: either
(1) guard CreatePromise with the running state (check the Start/Stop-running
flag in Start and refuse/fail fast in CreatePromise), or (2) move the
el.wg.Add(1) registration to a point that happens before the auto-stop goroutine
is started (or start the auto-stop goroutine only after an initial reservation),
or (3) disable auto-stop when asynchronous promises are expected; reference
CreatePromise, Start, el.wg.Add(1), and the auto-stop goroutine to locate the
changes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant