Skip to content

Fantom's Debug tester sometimes dereferences a null runtime when many testers start at once: the inspector it starts by default has not posted the first task when loadScript drains the queue #59695

Open

Description

@YevheniiKotyrlo

Description

When I run Fantom's suites, some testers die of SIGSEGV before any test runs, with nothing on stdout or stderr. It happens more often the more testers run at once, so a full yarn fantom run on a machine with many cores meets it in a few suites.

The tester is a Debug build, and a Debug build turns the inspector on by default (ReactInstanceConfig.h L22-L28); the tester's main only ever sets enableInspector to true, for --inspectorPort (main.cpp L48-L56). With an inspector, ReactInstance gives the runtime scheduler an executor that buffers every task until the inspector's thread has registered the runtime and flushed it (ReactInstance.cpp L112-L150), and only then is the scheduler's event loop posted to the tester's StubQueue.

TesterAppDelegate::loadScript schedules the task that sets runtime_, drains the queue once, and loadScriptAndRunTests then reads runtime_ (TesterAppDelegate.cpp L168-L192). When the inspector's thread is late, which CPU contention makes likely, the queue is still empty when it is drained: runtime_ stays null and line 187 dereferences it.

In a 0.88.0 build of the tester I looked at a core of one of these crashes: runtime_ was null, the queue was empty, and the scheduler had isEventLoopScheduled_ = true with its tasks still queued. A StubQueue that logged the thread of each post showed the InspectorThread posting before the drain in the runs that passed, and no post from it before the drain in the run that crashed.

With enableInspector = false in the tester's config, so the inspector starts only for --inspectorPort, the same loop never crashes.

Steps to reproduce

  1. git clone https://github.com/YevheniiKotyrlo/react-native-fantom-reproducer && cd react-native-fantom-reproducer

  2. ./fantom/reproduce.sh concurrent-testers-null-runtime (needs Docker). It clones react/react-native at main, builds Fantom's tester as CI does, runs View-itest.js once through yarn fantom to bundle it, then starts 16 testers at once on that bundle, 200 times, with the arguments yarn fantom gives a test:

    private/react-native-fantom/build/tester/fantom_tester --bundlePath <bundle> --featureFlags '{}' --minLogLevel error

    Then it applies fantom/tester-inspector-off.patch, which sets .enableInspector = false in main.cpp, rebuilds, and runs the same 200 rounds.

Expected: every tester exits 0.

Actual: on main at 97f6997, 74 of the 3,200 stock testers exited with status 139 and printed nothing; with the inspector off, all 3,200 exited 0.

React Native Version

0.88.0 (reproduced on main at 97f6997)

Affected Platforms

Build - Linux

Output of npx @react-native-community/cli info

System:
  OS: Linux 6.18 Ubuntu 22.04.5 LTS 22.04.5 LTS (Jammy Jellyfish)
  CPU: (24) x64 AMD Ryzen 9 9950X3D 16-Core Processor
  Memory: 116.43 GB / 125.63 GB
  Shell:
    version: 5.1.16
    path: /bin/bash
Binaries:
  Node:
    version: 22.14.0
    path: /usr/local/bin/node
  Yarn:
    version: 1.22.22
    path: /usr/local/bin/yarn
  npm:
    version: 10.9.2
    path: /usr/local/bin/npm
  Watchman: Not Found
SDKs:
  Android SDK:
    API Levels:
      - "37"
    Build Tools:
      - 37.0.0
    Android NDK: 28.2.13676358
IDEs:
  Android Studio: Not Found
Languages:
  Java:
    version: 17.0.20.1
    path: /usr/bin/javac
  Ruby:
    version: 3.0.2
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Stacktrace or Logs

react-native 97f6997ee347f9e406787da9c97fde687cebdda1, committed 2026-10-30T21:22:43+00:00
clang version 23.1.3 (https://github.com/llvm/llvm-project 0d261d1ca552c95a8f007e061c787ac7132fbcbc)
bundle private/react-native-fantom/.out/js-builds/run-1793396836098/1793396849817-View-itest.js.bundle.js
== the stock tester
round 25, tester 10: exit status 139, 0 bytes on stdout and stderr
round 25, tester 13: exit status 139, 0 bytes on stdout and stderr
round 26, tester 7: exit status 139, 0 bytes on stdout and stderr
round 35, tester 16: exit status 139, 0 bytes on stdout and stderr
round 37, tester 16: exit status 139, 0 bytes on stdout and stderr
...
3200 testers, 74 exited non-zero
== the tester with enableInspector = false
3200 testers, 0 exited non-zero

MANDATORY Reproducer

https://github.com/YevheniiKotyrlo/react-native-fantom-reproducer

Screenshots and Videos

No response

Activity

  1. YevheniiKotyrlo commented on Oct 30, 2026

    @YevheniiKotyrlo
    ContributorAuthor

    This is the part of the patch I carry for this that main still needs (its StubQueue lock is already there since #59015): the tester starts the inspector only for --inspectorPort, so loadScript drains a queue that holds the first task.

    diff --git a/private/react-native-fantom/tester/src/main.cpp b/private/react-native-fantom/tester/src/main.cpp
    --- a/private/react-native-fantom/tester/src/main.cpp
    +++ b/private/react-native-fantom/tester/src/main.cpp
    @@ -48,6 +48,9 @@ int main(int argc, char* argv[]) {
       auto config = ReactInstanceConfig{
           .appId = "com.meta.reactnative.fantom",
           .deviceName = "RN Fantom",
    +      // A Debug build's default starts the inspector, whose thread schedules
    +      // JavaScript work while loadScript drains the queue.
    +      .enableInspector = false,
       };
     
       if (AppSettings::inspectorPort.has_value()) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions