Files
Pieter De Baets 3ca6ea3eca Buffer async CallInvoker work with module calls (#58313)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58313

In bridgeless, native reaches JS by two routes that end in the same
`RuntimeScheduler` queue but get there differently. `callFunctionOnModule` goes
through the instance's `BufferedRuntimeExecutor`; the `CallInvoker` goes
straight to `scheduleTask`. The CallInvoker therefore skips the buffer entirely
and can reach the runtime while a module call issued earlier is still parked,
unflushed, because the bundle is mid-evaluation. Native code that issues both
cannot rely on the order it issued them in, and `Task` is a min-heap on
`now() + timeout(priority)` with no insertion tiebreak, so equal priorities do
not settle it either.

Gives the two channels the same buffering. `BufferedRuntimeExecutor` gains a
priority-carrying `execute`, so work routed through it keeps the scheduler
priority it was submitted with instead of collapsing to the executor default,
and buffered work from both overloads stays in one submission-ordered stream.
`BufferedCallInvoker` sits on that executor and becomes the bridgeless
`jsCallInvoker` on Android, iOS and macOS.

`invokeSync` deliberately keeps going straight to the scheduler: a synchronous
call cannot wait for a flush that only happens once the bundle has run.

Behind `enableBufferedCallInvoker`, default true. `ReactInstance` picks between
the buffered invoker and the existing `RuntimeSchedulerCallInvoker` in one
place, so the platform call sites are identical either way and the change is
revertible at runtime — it moves when native-issued async work first reaches JS
during startup, which is the intended contract but affects every native module.

One lifetime hazard this surfaces, worth knowing about beyond this diff:
`BufferedRuntimeExecutor` reaches the scheduler through a raw pointer captured
at construction, which is safe only while the owning instance is alive. A
CallInvoker is routinely held across instance teardown, so `BufferedCallInvoker`
guards every async dispatch on a weak reference to the scheduler and drops the
work when it has expired — the same contract `RuntimeSchedulerCallInvoker` has.
Without that guard this reliably segfaults on a reload.

Changelog:
[General][Changed] - Async `CallInvoker` work is now buffered alongside callable module calls, so it no longer runs before the JS bundle has finished evaluating

Reviewed By: rubennorte

Differential Revision: D118456662

fbshipit-source-id: 7c6ccb595d69595721092eeb84b4724809140496
2026-09-04 12:39:12 -07:00
..

scripts/cxx-api

Python build pipeline for React Native's C++ (and Objective-C) API snapshots.

Overview

scripts/cxx-api generates human-readable snapshots of React Native's public C++ API surface. It uses Doxygen to parse C/C++/Objective-C headers and a custom Python parser to produce a simplified, sorted representation of every public symbol.

The pipeline produces one .api snapshot file per configured API view × variant combination:

Snapshot Description
ReactCommonDebugCxx.api Platform-independent C++ API (debug)
ReactCommonReleaseCxx.api Platform-independent C++ API (release)
ReactAndroidDebugCxx.api Android-specific C++ API (debug)
ReactAndroidReleaseCxx.api Android-specific C++ API (release)
ReactAppleDebugCxx.api Apple-specific C++/Obj-C API (debug)
ReactAppleReleaseCxx.api Apple-specific C++/Obj-C API (release)

For each view, debug and release variants are generated with different preprocessor definitions (e.g. REACT_NATIVE_DEBUG vs NDEBUG), since #ifdef guards in the source headers can produce a different public API surface per variant.

Snapshot files are committed to the repo under scripts/cxx-api/api-snapshots/.

Usage

Generate snapshots

Maintainers should run this command whenever making intentional C++ API changes:

python -m scripts.cxx-api.parser

Validate snapshots against committed baseline

This mode generates snapshots to a temporary directory and compares them against the committed .api files. It is designed for CI:

python -m scripts.cxx-api.parser --validate

If any snapshot differs, a unified diff is printed and the process exits with a non-zero status. To fix a failing validation, regenerate the snapshots with python -m scripts.cxx-api.parser and commit the updated .api files.

How it works

The pipeline has two main stages:

1. Doxygen XML generation

Doxygen is configured via a generated config file (built from .doxygen.config.template) with the input directories, exclude patterns, and preprocessor definitions specified in config.yml. It outputs XML describing every symbol found in the headers.

2. Snapshot parsing

The Python parser (parser/) reads the Doxygen XML output and builds a scope tree of the public API surface. The tree is then serialized to a deterministically sorted, human-readable .api text format.

When to use it

The snapshot should be regenerated whenever making intentional changes to the public C++ API surface. This includes additions, removals, and changes to files located in:

  • xplat/js/react-native-github/
  • xplat/js/react-native-github/ReactCommon/
  • xplat/js/react-native-github/ReactAndroid/
  • xplat/js/react-native-github/ReactApple/
  • xplat/js/react-native-github/Libraries/

Configuration

All API views and their variants are defined in config.yml. Each view specifies:

Field Description
inputs Directories to scan for headers
exclude_patterns Glob patterns for files to skip
definitions Preprocessor macros to define
variants Named build variants (e.g. debug/release) with extra definitions
codegen Optional codegen platform (android, ios) to generate TurboModule/Component headers before scanning
private_directories Directories whose headers are scanned (they may be transitively included) but should not contribute public symbols. If any public API entity is defined in a private directory, a warning is printed to help catch accidental API exposure.

Snapshot format

The .api files use a minimal pseudo-C++ syntax designed for easy diffing:

namespace facebook::react {
  class ComponentDescriptor {
    public ComponentDescriptor(ComponentDescriptorParameters params);
    public ComponentHandle getComponentHandle();
  }

  enum class AccessibilityRole {
    None = 0,
    Button = 1,
  }
}
  • Scopes and members are sorted alphabetically.
  • Access specifiers (public, protected) are preserved.
  • Template parameters are included.
  • Doc comments and source file names are stripped.