Repository navigation
On the cxx platform, every TurboModuleRegistry lookup answers a new C++ TurboModule instance, where Android and iOS keep one per name #59672
Description
Description
On React Native's cxx platform (ReactCxxPlatform, which Fantom's tester runs), ReactCxxTurboModuleProvider::operator() returns a new std::shared_ptr<TurboModule> for every lookup: each provider is called again and each built-in module is std::make_shared again, with no cache by name. ReactHost installs that provider as TurboModuleBinding's module provider directly, and TurboModuleBinding::getModule caches only the module's jsRepresentation_, on the instance it was just handed, so two TurboModuleRegistry.get(name) calls answer two different objects. Android's TurboModuleManager (turboModuleCache_) and iOS's RCTTurboModuleManager keep one instance per name, and TurboModuleBinding::getModule itself says "TurboModules are cached by name in TurboModuleManagers".
A C++ TurboModule that keeps state, a subscription or a registered commit hook therefore behaves on cxx as it does on no device: its state lives only as long as JavaScript holds the one object it was set on, and each lookup can register another hook. I met it with a C++ module that installs a commit hook on its first use; in Fantom's tester the hook went away with the object one lookup returned.
Steps to reproduce
git clone https://github.com/YevheniiKotyrlo/react-native-fantom-reproducer && cd react-native-fantom-reproducer./fantom/reproduce.sh cxx-turbomodule-per-lookup(needs Docker). It clonesreact/react-nativeatmain, builds Fantom's tester, and runs this test, its license and Flow header left out here, aspackages/react-native/Libraries/TurboModule/__tests__/TurboModuleLookup-itest.js:
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import * as TurboModuleRegistry from '../TurboModuleRegistry';
describe('TurboModuleRegistry', () => {
it('answers the same module for every lookup of one name', () => {
expect(TurboModuleRegistry.get('SourceCode')).not.toBeNull();
const first = TurboModuleRegistry.get('SourceCode');
const second = TurboModuleRegistry.get('SourceCode');
expect(first === second).toBe(true);
});
});The same on a checkout of main: put that file in place, then yarn workspace @react-native/fantom build and yarn fantom packages/react-native/Libraries/TurboModule/__tests__/TurboModuleLookup-itest.js. The reproducer builds with Clang 23 and CXXFLAGS=-Wno-error=deprecated-literal-operator for #59667; the defect does not depend on the compiler.
React Native Version
0.88.0 (reproduced on main at 54905a2)
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
FAIL fantom packages/react-native/Libraries/TurboModule/__tests__/TurboModuleLookup-itest.js (6.401 s)
TurboModuleRegistry (hermes)
✕ answers the same module for every lookup of one name (1 ms)
● TurboModuleRegistry (hermes) › answers the same module for every lookup of one name
Expected true but received false.
at construct (native)
at apply (native)
MANDATORY Reproducer
https://github.com/YevheniiKotyrlo/react-native-fantom-reproducer
Screenshots and Videos
No response
Activity