Summary:
During setup, TurboModules may synchronously dispatch to the main queue, if they require main queue setup. This is dangerous because it could cause the app to deadlock during TurboModule require. This is why D21654637 (https://github.com/facebook/react-native/commit/e206e34175c091a753c0e733abeda41b662241d4) added a warning aginst this. However, this diff had a mistake. We only want to display the warning if TurboModule eager initialization is enabled, because then, we can eagerly initialize the TurboModules before the bridge starts to avoid the problem. D21654637 (https://github.com/facebook/react-native/commit/e206e34175c091a753c0e733abeda41b662241d4) instead showed the warning if TurboModule eager init **wasn't** enabled. This isn't useful, because there's no way to avoid the problem with TurboModu eager initialization off.
Changelog: [Internal]
Reviewed By: sammy-SC
Differential Revision: D22529780
fbshipit-source-id: 15238483758b66b1a6addcad948203c64dca96ad
Summary:
TurboModule eager initialization is a bit dangerous if we get it wrong, which we did (twice): T69449176.
This diff gates TurboModule eager init behind a MC, so that we can control (i.e: turn off/on, and do gradually rollout of) TurobModule eager initialization in isolation from the larger TurboModules experiment.
Changelog: [Internal]
Reviewed By: PeteTheHeat
Differential Revision: D22460359
fbshipit-source-id: 3b8dce0529f1739bd68b8b16d6a28aa572d82c2c
Summary:
When you require a TurboModule on thread that isn't the main thread, but the TurboModule requires main queue setup, we are forced to `dispatch_sync` the set up to the main queue. This is hazardous, because it can lead to deadlocks. Therefore, I'm migrating over a warning from the legacy infra that warns against this use-case.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D21654637
fbshipit-source-id: 99dc45708c533603d2111fe6163d40e807d2a513
Summary:
The changes in this diff are inconsequential. Just thought it was weird that TurboModuleManager was responsible for assigning itself to the bridge. Seems like that should be done by an entity that owns both the bridge and the TurboModuleManager.
Changelog:
[Internal]
Reviewed By: PeteTheHeat
Differential Revision: D22406172
fbshipit-source-id: cb32f097f4d377a3b85a6d165b7b06be8e6a185b
Summary:
## Why?
1. RCTTurboModuleLookupDelegate sounds a bit nebulous.
2. In JS and Java, we use a `TurboModuleRegistry` interface to require TurboModules. So, this diff will make JS, Java, and ObjC consistent.
Changelog:
[Internal]
Reviewed By: PeteTheHeat
Differential Revision: D22405754
fbshipit-source-id: 30c85c246b39d198c5b8c6ca4432a3196ca0ebfd
Summary:
## Context
1. In FBReactModule jsExecutorForBridge, we asynchronously initialize a list of TurboModules on the main queue: https://fburl.com/diffusion/i56wi3px
2. After initializing the bridge, we start executing the JS bundle, here: https://github.com/facebook/react-native/blob/e23e9328aa164d0a70fe4f16042c982e7801d924/React/CxxBridge/RCTCxxBridge.mm#L414-L417. Since bridge initialization knows nothing about TurboModule eager initialization, this happens concurrently with 1, and starts requiring NativeModules/TurboModules on the JS thread.
## The Race
1. Both the main thread and the JS thread race to create a TurboModule that requires main queue setup.
2. The JS thread wins, and starts creating the TurboModule. Meanwhile, the main thread blocks, waiting on a signal here, in RCTTurboModuleManager: https://github.com/facebook/react-native/blob/e23e9328aa164d0a70fe4f16042c982e7801d924/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm#L430
3. The JS thread tries to dispatch_sync to the main queue to setup the TurboModule because the TurboModule requires main queue setup, here: https://github.com/facebook/react-native/blob/e23e9328aa164d0a70fe4f16042c982e7801d924/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm#L402
4. We deadlock.
## The fix
Succinctly, NativeModule eager initialization finishes before execute the JS bundle, but TurboModule initialization doesn't. This diff corrects that mistake.
The changes in this diff:
1. The RN application via the TurboModuleManager delegate can now optionally provide the names of all eagerly initialized TurboModules by implementing two methods `getEagerInitModuleNames`, `getEagerInitMainQueueModuleNames`.
2. The TurboModuleManager grabs these two lists from the delegate, and exposes them to its owner via the `RCTTurboModuleRegistry` protocol.
3. The RCTCxxBridge, which already owns a `id<RCTTurboModuleRegistry>` object, uses it to eagerly initialize the TurboModules in these two lists with the correct timing requirements.
This is exactly how we implement eager initialization in Android.
**Note:** Right now, phase one and two of TurboModule eager initialization happen after phase one and two of NativeModule eager initialization. We could make the timing even more correct by initializing the TurboModules at the exact same time we initialize the NativeModules. However, that would require a bit more surgery on the bridge, and the bridge delegate. I think this is good enough for now.
Changelog:
[iOS][Fixed] - Fix TurboModule eager init race
Reviewed By: PeteTheHeat
Differential Revision: D22406171
fbshipit-source-id: 4715be0bceb478a8e4aa206180c0316eaaf287e8
Summary:
There are two ways to make a NativeModule method execute synchronously:
- Declare the NativeModule method to be synchronous (i.e: use `RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD`).
- Make the NativeModule synchronous (i.e: make its method queue `RCTJSThread`). This way, all its methods are synchronous.
`RCTNativeModule` executes all synchronous methods on the JS thread:
- Executing an async methods on a sync module: https://git.io/JfRPj
- Executing a sync method: https://git.io/JfRXe
However, in TurboModules we block the JS thread, execute the method on the NativeModule's method queue, and then unblock the JS thread. While this approach is thread-safe, and arguably the correct way to dispatch sync methods, it's also much slower than the alternative. Therefore, this diff migrates the legacy behaviour to the TurboModule system.
## Special Case: getConstants()
When an ObjC NativeModule requires main queue setup, and it exports constants, we execute its `constantsToExport` method on the main queue (see: [RCTModuleData gatherConstants](https://github.com/facebook/react-native/blob/c8d678abcf93fd3f6daf4bebfdf25937995c1fdf/React/Base/RCTModuleData.mm#L392-L402)). I replicated this behaviour with TurboModules.
Changelog:
[iOS][Fixed] - Execute ObjC TurboModule async method calls on JS thread for sync modules
Reviewed By: fkgozali
Differential Revision: D21602096
fbshipit-source-id: 42d07b7ad000abeac27091dc3ec440e3836d2eae
Summary:
If a NativeModule requires main queue setup, its `getConstants()` method must be executed on the main thead. The legacy NativeModule infra takes care of this for us. With TurboModules, however, all synchronous methods, including `getConstants()`, execute on the JS thread. Therefore, if a TurboModule requires main queue setup, and exports constants, we must execute its `getConstants()` method body on the main queue explicitly.
**Notes:**
- The changes in this diff should be a noop when TurboModules is off, because `RCTUnsafeExecuteOnMainQueueSync` synchronously execute its block on the current thread if the current thread is the main thread.
- If a NativeModule doens't have the `requiresMainQueueSetup` method, but has the `constantsToExport` method, both NativeModules and TurboModules assume that it requires main queue setup.
## Script
```
const exec = require("../lib/exec");
const abspath = require("../lib/abspath");
const relpath = require("../lib/relpath");
const readFile = (filename) => require("fs").readFileSync(filename, "utf8");
const writeFile = (filename, content) =>
require("fs").writeFileSync(filename, content);
function main() {
const tmFiles = exec("cd ~/fbsource && xbgs -n 10000 -l constantsToExport")
.split("\n")
.filter(Boolean);
const filesWithoutConstantsToExport = [];
const filesWithConstantsToExportButNotGetConstants = [];
const filesExplicitlyNotRequiringMainQueueSetup = [];
tmFiles
.filter((filename) => {
if (filename.includes("microsoft-fork-of-react-native")) {
return false;
}
return /\.mm?$/.test(filename);
})
.map(abspath)
.forEach((filename) => {
const code = readFile(filename);
const relFilename = relpath(filename);
if (!/constantsToExport\s*{/.test(code)) {
filesWithoutConstantsToExport.push(relFilename);
return;
}
if (!/getConstants\s*{/.test(code)) {
filesWithConstantsToExportButNotGetConstants.push(relFilename);
return;
}
if (/requiresMainQueueSetup\s*{/.test(code)) {
const requiresMainQueueSetupRegex = /requiresMainQueueSetup\s*{\s*return\s+(?<requiresMainQueueSetup>YES|NO)/;
const requiresMainQueueSetupRegexMatch = requiresMainQueueSetupRegex.exec(
code
);
if (!requiresMainQueueSetupRegexMatch) {
throw new Error(
"Detected requiresMainQueueSetup method in file " +
relFilename +
" but was unable to parse the method return value"
);
}
const {
requiresMainQueueSetup,
} = requiresMainQueueSetupRegexMatch.groups;
if (requiresMainQueueSetup == "NO") {
filesExplicitlyNotRequiringMainQueueSetup.push(relFilename);
return;
}
}
const getConstantsTypeRegex = () => /-\s*\((?<type>.*)\)getConstants\s*{/;
const getConstantsTypeRegexMatch = getConstantsTypeRegex().exec(code);
if (!getConstantsTypeRegexMatch) {
throw new Error(
`Failed to parse return type of getConstants method in file ${relFilename}`
);
}
const getConstantsType = getConstantsTypeRegexMatch.groups.type;
const getConstantsBody = code
.split(getConstantsTypeRegex())[2]
.split("\n}")[0];
const newGetConstantsBody = `
__block ${getConstantsType} constants;
RCTUnsafeExecuteOnMainQueueSync(^{${getConstantsBody
.replace(/\n/g, "\n ")
.replace(/_bridge/g, "self->_bridge")
.replace(/return /g, "constants = ")}
});
return constants;
`;
writeFile(
filename,
code
.replace(getConstantsBody, newGetConstantsBody)
.replace("#import", "#import <React/RCTUtils.h>\n#import")
);
});
console.log("Files without constantsToExport: ");
filesWithoutConstantsToExport.forEach((file) => console.log(file));
console.log();
console.log("Files with constantsToExport but no getConstants: ");
filesWithConstantsToExportButNotGetConstants.forEach((file) =>
console.log(file)
);
console.log();
console.log("Files with requiresMainQueueSetup = NO: ");
filesExplicitlyNotRequiringMainQueueSetup.forEach((file) =>
console.log(file)
);
}
if (!module.parent) {
main();
}
```
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D21797048
fbshipit-source-id: a822a858fecdbe976e6197f8339e509dc7cd917f
Summary:
## Motivation
We got this crash T67304907, which shows a `EXC_BAD_ACCESS / KERN_INVALID_ADDRESS` when calling this line:
```
NativeModulePerfLogger::getInstance().asyncMethodCallBatchPreprocessStart();
```
There are no arguments in that call, so I figured the only error could be when we try to invoke `getInstance()` or `asyncMethodCallBatchPreprocessStart()`.
This diff:
1. Removes the `NativeModulePerfLogger::getInstance()` bit. Now NativeModulePerfLogger is used via regular static C functions. So, there's no way that simply invoking one of the logging functions crashes the application: there's no vtable lookup.
2. Inside each logging function, when perf-logging is disabled, the global perflogger should be `nullptr`. This diff makes it so that in that case, we won't execute any code in the control group of the perf-logging experiment.
## Changes
**How do we enable NativeModule perf-logging?**
- Previously:
- `NativeModulePerfLogger::setInstance(std::make_shared<FBReactNativeModulePerfLogger>(...))`
- `TurboModulePerfLogger::setInstance(std::make_shared<FBReactNativeModulePerfLogger>(...))`.
- Now:
- `BridgeNativeModulePerfLogger::enableLogging(std::make_unique<FBReactNativeModulePerfLogger>(...))`
- `TurboModulePerfLogger::enableLogging(std::make_unique<FBReactNativeModulePerfLogger>(...))`
**How do we do NativeModule perf-logging now?**
- Previously:
- `NativeModulePerfLogger::getInstance().command(...args)`
- `TurboModulePerfLogger::getInstance().command(...args)`.
- Now:
- `BridgeNativeModulePerfLogger::command(...args)`
- `TurboModulePerfLogger::command(...args)`.
The benefit of this approach is that each method in `BridgeNativeModulePerfLogger` is guarded with an if check. Example:
```
void moduleCreateConstructStart(const char *moduleName, int32_t id) {
NativeModulePerfLogger *logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateConstructStart(moduleName, id);
}
}
```
Therefore, we don't actually execute any code when perf-logging is disabled.
Changelog:
[Internal]
Reviewed By: fkgozali
Differential Revision: D21669888
fbshipit-source-id: 80c73754c430ce787404b563878bad146295e01f
Summary:
## Motivation
This rename will fix the following CircleCI build failures:
- [test_ios_unit_frameworks](https://circleci.com/gh/facebook/react-native/150473?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link)
- [test_ios_detox_frameworks](https://circleci.com/gh/facebook/react-native/150474?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link)
## Investigation
We have 4 podspec targets that map to the same header namespace (i.e: `header_dir`) `ReactCommon`:
- **New:** `React-perflogger`: Directory is `ReactCommon/preflogger`, and contains `NativeModulePerfLogger.{h,cpp}`.
- `React-runtimeexecutor`: Directory is `ReactCommon/runtimeexecutor`, and contains only `RuntimeExecutor.h`
- `React-callinvoker`: Directory is `ReactCommon/callinvoker`, and contains only `CallInvoker.h`
- `ReactCommon/turbomodule/core`: Directory is `ReactCommon/turbomodule`, and contains C++ files, as well has header files.
**The problem:**
We couldn't import headers from `React-perflogger` in `ReactCommon/turbomodule/core` files.
**The cause:**
I'm not entirely sure why, but I was able to discern the following two rules by playing around with the podspecs:
1. If your podspec target has a cpp file, it'll generate a framework when `USE_FRAMEWORKS=1`.
2. Two different frameworks cannot map to the same `module_name` or `header_dir`. (Why? No clue. But something breaks silently when this is the case).
So, this is what happened when I landed `React-perflogger` (D21443610):
1. The TurboModules code generates the `ReactCommon` framework that uses the `ReactCommon` header namespace.
2. `React-runtimeexecutor` and `React-callinvoker` also used the `ReactCommon` header namespace. However, neither generate a framework because of Rule 1.
3. When I comitted `React-perflogger`, I introduced a second framework that competed with the `ReactCommon` framework (i.e: TurboModules code) for the `ReactCommon` header namespace. Rule 2 violation.
## Thoughts on renaming
- `<perflogger/NativeModulePerfLogger.h>` is too generic, and the `perflogger` namepsace is used internally within FB.
- `<react/perflogger/NativeModulePerfLogger.h>` matches our fabric header format, but I'm pretty sure that slashes aren't allowed in `header_dir`: I tested this and it didn't work. IIRC, only alphanumeric and underscore are valid characters for `header_dir` or `module_name`. So, I opted to just use `reactperflogger`.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D21598852
fbshipit-source-id: 60da5d0f7758eaf13907a080b7d8756688f40723
Summary:
## Motivation
This rename will fix the following CircleCI build failures:
- [test_ios_unit_frameworks](https://circleci.com/gh/facebook/react-native/150473?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link)
- [test_ios_detox_frameworks](https://circleci.com/gh/facebook/react-native/150474?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link)
## Investigation
We have 4 podspec targets that map to the same header namespace (i.e: `header_dir`) `ReactCommon`:
- **New:** `React-perflogger`: Directory is `ReactCommon/preflogger`, and contains `NativeModulePerfLogger.{h,cpp}`.
- `React-runtimeexecutor`: Directory is `ReactCommon/runtimeexecutor`, and contains only `RuntimeExecutor.h`
- `React-callinvoker`: Directory is `ReactCommon/callinvoker`, and contains only `CallInvoker.h`
- `ReactCommon/turbomodule/core`: Directory is `ReactCommon/turbomodule`, and contains C++ files, as well has header files.
**The problem:**
We couldn't import headers from `React-perflogger` in `ReactCommon/turbomodule/core` files.
**The cause:**
I'm not entirely sure why, but I was able to discern the following two rules by playing around with the podspecs:
1. If your podspec target has a cpp file, it'll generate a framework when `USE_FRAMEWORKS=1`.
2. Two different frameworks cannot map to the same `module_name` or `header_dir`. (Why? No clue. But something breaks silently when this is the case).
So, this is what happened when I landed `React-perflogger` (D21443610):
1. The TurboModules code generates the `ReactCommon` framework that uses the `ReactCommon` header namespace.
2. `React-runtimeexecutor` and `React-callinvoker` also used the `ReactCommon` header namespace. However, neither generate a framework because of Rule 1.
3. When I comitted `React-perflogger`, I introduced a second framework that competed with the `ReactCommon` framework (i.e: TurboModules code) for the `ReactCommon` header namespace. Rule 2 violation.
## Thoughts on renaming
- `<perflogger/NativeModulePerfLogger.h>` is too generic, and the `perflogger` namepsace is used internally within FB.
- `<react/perflogger/NativeModulePerfLogger.h>` matches our fabric header format, but I'm pretty sure that slashes aren't allowed in `header_dir`: I tested this and it didn't work. IIRC, only alphanumeric and underscore are valid characters for `header_dir` or `module_name`. So, I opted to just use `reactperflogger`.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D21585006
fbshipit-source-id: e3339273af5dfd65a1454d87213d1221de6a4651
Summary:
## Description
This diff introduces `NativeModulePerfLogger`, its BUCK, Cocoapod, android-ndk targets. This diff also wires up those targets into the React Native bridge and TurboModules targets, so that we get signal on if the code compiles.
This diff also introduces `TurboModulePerfLogger`, which is a namespace that holds the `NativeModulePerfLogger` that'll do perf-logging for TurboModules.
## How will perflogging work on iOS?
1. Each application will, during React Native initialization, create a NativeModule perf logger.
2. If TurboModules are enabled, we'll call `TurboModulePerfLogger::setInstance(perfLogger)`. If TurboModules are disabled, we'll call `NativeModulePerfLogger::setInstance(perfLogger)`.
3. TurboModules, when they're created and used, will log events via `TurboModulePerfLogger::getInstance()`. NativeModules (i.e: bridge modules), when they're created and used, will log events via the `NativeModulePerfLogger::getInstance()`.
> **Note:** The NativeModule system will log events for non-TurboModules as well. Maybe we should log events for only NativeModules that conform to the `TurboModule` interface, when TurboModules are disabled. This'll ensure a fair comparison between the two systems.
## How will perflogging work on Android?
Please see the subsequent diff.
allow-large-files
Changelog:
[Both][Added] - Introduce `NativeModulePerfLogger`
Reviewed By: PeteTheHeat
Differential Revision: D21318053
fbshipit-source-id: 6ddf5b5a80bdc4076d2dd6588067e2b0ec8c2c6b
Summary:
Move and create an empty rule that redirects as well, to handle //arvr rules
Need to do this way, since ovrsource sync rules are in different repo.
allow_many_files
allow-large-files
Steps:
- [X] Move glog from xplat/third-party to /third-party
- [ ] Update references in ovrsource to translate to //third-party instead of //xplat/third-party
- [ ] Get rid of temporary rule
- [ ] Update fbsource/third-party/glog to 0.3.5 (what we have in ovrsource)
Changelog: [Internal] Update reference for glog from xplat/third-party to /third-party.
Reviewed By: yfeldblum
Differential Revision: D21363584
fbshipit-source-id: c1ffe2dd615077170b03d98dcfb77121537793c9
Summary:
While #import is common in Objective C, it's a vendor specific extension in C++, only supported by GCC/Clang, and only when -pedantic is off. Its use causes build breaks with MSVC. Replace it with the standard #include.
## Changelog
[Internal] [Fixed] - Don't use #import in C++ Code
Pull Request resolved: https://github.com/facebook/react-native/pull/28825
Test Plan: We've ben running this change within react-native-windows for some time.
Differential Revision: D21391233
Pulled By: shergin
fbshipit-source-id: c0f94f314c46d6ac24067bbdcd5aaaeec9da283f
Summary:
## Problems:
In my investigation of T65656635, I realized that the TurboModule system has a number of problems:
- In TurboModules, we use 1 lock to create n TurboModules. We should change this setup to n locks for n TurboModules. This way, two threads creating two different NativeModules don't compete for the same lock. Also, this is how it's done in Android (TurboModules & NativeModules), and iOS (NativeModules).
- In TurboModules, we don't calculate "requires main queue setup" faithfully. In the legacy system, if a NativeModule has a custom `init` method or a custom `constantsToExport` method, it "requires main queue setup" with a warning.
- In TurboModules, we don't create the NativeModule on the main queue, if "requires main queue setup" is true. Instead, the NativeModule is always created on the thread that requires it.
- In TurboModules, we don't perform any concurrency control around `id<RCTTurboModule>` setup. We should.
## What this diff does
In this diff, I fixed all the aforementioned issues by re-implementing `provideRCTTurboModule:`.
**Algorithm Notes:**
- **Gist:** When `n` threads race to create NativeModule `x`, only the first thread creates and sets up `x`. All others are told to wait. Once the creator thread finishes its job, it notifies all other waiting threads, which then wake up and return the newly created NativeModule. This algorithm was initially implemented in NativeModules for Android inside (ModuleHolder.java). I modified and implemented it for TurboModules for Android, and now this diff implements it for TurboModules for iOS.
- The TurboModule cache is replace with a TurboModuleHolder map. A TurboModuleHolder manages the creation lifecycle of a TurboModule, and holds a condition variable and mutex for doing concurrency control around it. When the bridge invalidates, in TurboModuleManager, we set the `invalidating` flag to true, which prevents the insertion of new entries into the TurboModuleHolder map.
- I added a `std::mutex` to serialize calls into the TurboModuleManagerDelegate, so we don't get races if the delegate isn't thread-safe.
Changelog:
[iOS][Fixed] - Re-implement RCTTurboModuleManager provideRCTTurboModule:
Reviewed By: shergin
Differential Revision: D21170099
fbshipit-source-id: 8792812c2237d3bfc80c9834c818e011de85b0ea
Summary:
Adds `RootTag` as a valid type for arguments and return types in TurboModules (on both Android and iOS).
This will enable us to change `RootTag` into an opaque type. There are two compelling reasons to do this:
- JavaScript will no longer be able to safely depend on `RootTag` being a number (which means we can change this in the future).
- Call sites using `unstable_RootTagContext` will can get a `RootTag`, but call sites using the legacy `context.rootTag` will not. This means the opaque type will give us a strategy for migrating away from legacy context and eventually making `unstable_RootTagContext` the only way to access `RootTag`.
Changelog:
[Internal]
(Note: this ignores all push blocking failures!)
Reviewed By: RSNara
Differential Revision: D21127170
fbshipit-source-id: baec9d7ad17b2f8c4527f1a84f604fc0d28b97eb
Summary:
## Summary
Please check out D21035208.
## Changes
- Update `RCTTurboModuleManagerDelegate getTurboModule:instance:jsInvoker:nativeInvoker:perfLogger` to use `RCTTurboModuleManagerDelegate getTurboModule:(const ObjCTurboModule::InitParams)`
- Update all implementations of `RCTTurboModuleManagerDelegate` in accordance with this API change
Changelog:
[iOS][Changed] - Make RCTTurboModuleManagerDelegate create TurboModules via ObjCTurboModuleManager::InitParams
Reviewed By: PeteTheHeat
Differential Revision: D21036272
fbshipit-source-id: c16002c47db26e2ba143fc1080afe9e2fe1e7816
Summary:
## Summary
Please check out D21035208.
## Changes
- `ObjCTurboModule::ObjCTurboModule` changed to accept a bag of arguments `const ObjCTurboModule::InitParams` instead of an argument list.
- TurboModule iOS codegen scripts updated to generated `ObjCTurboModule` subclasses that accept a `const ObjCTurboModule::InitParams` object in their constructor, and forward it to `ObjCTurboModule::ObjCTurboModule`.
- All manually checked in code-generated ObjC++ classes (i.e: RCTNativeSampleTurboModule, RCTTestModule, FBReactNativeSpec) are updated.
## Rationale
This way, the code-gen can remain constant while we add, remove, or modify the arguments passed to ObjCTurboModule.
## Commands run
```
function update-codegen() {
pushd ~/fbsource && js1 build oss-native-modules-specs -p ios && js1 build oss-native-modules-specs -p android && popd;
}
> update-codegen
```
Changelog:
[iOS][Changed] Update ObjCTurboModule to use ObjCTurboModule::InitParams
Reviewed By: PeteTheHeat
Differential Revision: D21036266
fbshipit-source-id: 6584b0838dca082a69e8c14c7ca50c3568b95086
Summary:
## Problem
Every time we want to add, remove, or change the data passed to JavaTurboModule's constructor, we have to modify the C++ TurboModule codegen. (The same is true of `ObjCTurboModule`).
**Why was this necessary?**
- `JavaTurboModule` is effectively an abstract class whose constructor is always invoked by code-generated C++ classes. These C++ code-generated class constructors accept an argument list, and manually foward each and every item in that list to `JavaTurboModule::JavaTurboModule`.
## The fix
In this diff, I introduce a struct `JavaTurboModule::InitParams`, to represent a bag of arguments:
```
class JSI_EXPORT JavaTurboModule : public TurboModule {
public:
struct InitParams {
std::string moduleName;
jni::alias_ref<JTurboModule> instance;
std::shared_ptr<CallInvoker> jsInvoker;
std::shared_ptr<CallInvoker> nativeInvoker;
};
```
All `JavaTurboModules` will be created with an instance of this `InitParams` struct, instead of a list of arguments. Our code-generated C++ `jsi::HostObject` sublcasses will simply accept `InitParams` in their constructor, and forward it to `JavaTurboModule`'s constructor. This way, the codegen remains oblivious to what arguments JavaTurboModule requires.
## Okay, but why do we need this change now?
In the future, I plan to modify the constructor for `JavaTurboModule` to accept a performance logger, and a `RuntimeExecutor`. Similar modifications are planned for ObjC. For this reason, to avoid these four codemods, and any potential other codemods that occur because we're making modifications to `JavaTurboModule` or `ObjCTurboModule`, I'm launching this codemod, and the codemods in this stack.
## Misc Fix
- Previously, we were generating the TurboModule name from the Spec filename. This is incorrect because that name represents the spec name. Now, the name will be forwarded from TurboModuleManager in the `JavaTurboModule::InitParams` struct.
## Alternative implementations
I initially considered using `ContextContainer`, but decided against it because:
1. There are no type-safety guarantees.
2. I think it's a bit overkill for this scenario. We just need an opaque bag of data, and for our purposes a simple struct does the job fine.
## Commands run
Reviewed By: fkgozali
Differential Revision: D21035208
fbshipit-source-id: 9542cafea192081bc34d337ab3a7a783083eb06c
Summary:
This diff:
1. Has ObjC NativeModules use the native `CallInvoker` to invoke JS -> native sync/async calls.
2. Integrates the native `CallInvoker` for each ObjC NativeModule with the bridge. This way, the bridge is informed of all JS -> native TurboModule method calls, and dispatches `onBatchComplete` appropriately.
Changelog:
[iOS][Fixed] Integrate ObjC TurboModules async method calls with the bridge
Reviewed By: fkgozali
Differential Revision: D20831545
fbshipit-source-id: da1cbb4ecef4cae85841ca7ef625ab8e380760cd
Summary:
## Problem:
Let `A` be the set of all ObjC NativeModules that neither provide nor reqeust a method queue.
The TurboModule system dispatches all method calls to NativeModules in `A` synchronously to the JS thread. Here is the relevant logic:
**RCTTurboModule.mm:**
Link: https://fburl.com/diffusion/nz9gqje8
```
jsi::Value performMethodInvocation(
// ...
)
{
// ...
dispatch_queue_t methodQueue = NULL;
if ([instance_ conformsToProtocol:protocol(RCTBridgeModule)] &&
[instance_ respondsToSelector:selector(methodQueue)]) {
methodQueue = [instance_ performSelector:selector(methodQueue)];
}
if (methodQueue == NULL || methodQueue == RCTJSThread) {
// This is the default mode of execution: on JS thread.
block();
} else if (methodQueue == dispatch_get_main_queue()) {
```
**Why does this end up happening?**
1. NativeModules that request a method queue have `synthesize methodQueue = _methodQueue` in their `implementation` section. This generates a `methodQueue` getter for the NativeModule, and also creates an ivar to back that getter. The TurboModule system generates a `dispatch_queue_t` and uses ObjC's KVC API to write to the ivar. So in the above logic, for NativeModules that provide a method queue, methodQueue will neither be `NULL` nor `RCTJSThread`, so we don't dispatch synchronously to the JS thread.
2. NativeModules that provide a method queue will return something that is not `NULL` or something that is `RCTJSThread`. If they return `NULL`, the infra will throw an error early. If they return `RCTJSThread`, we'll dispatch synchronously to the JS thread, as we should (...wait. For async NativeModule methods that dispatch to `RCTJSThread`, should we dispatch asynchronously to the JS thread, via jsInvoker? **Edit:** Nope: https://fburl.com/diffusion/ivt9b40s.). In all other cases, we dispatch to appropriately to the respective method queue.
3. For NativeModules that neither provide nor request a method queue (i.e: NativeModules in `A`), they don't implement the `methodQueue` selector. Therefore, we dispatch synchronously to the JS thread.
## The fix (Part 1):
The first step towards fixing this problem is to generate `dispatch_queue_t`s for NativeModules in `A`.
That's what this diff accomplishes.
Changelog:
[iOS][Fixed] - Create method queue for NativeModules that don't provide nor request one.
Reviewed By: fkgozali
Differential Revision: D20821054
fbshipit-source-id: 17a73550ad96766c5c7e719e28e1cc879e36465c
Summary:
`RCTTurboModuleManager` will create a native `CallInvoker` for each ObjC NativeModule. This `CallInvoker` will be used to dispatch calls from JS to native. Before passing the native `CallInvoker` to the `ObjCTurboModule`, it'll first use `RCTCxxBridge decorateNativeCallInvoker` to get a bridge-aware decorated native `CallInvoker`. That way, the bridge remains informed about async TurboModule method calls that took place since the last time it was flushed. This ensures that we don't end up dispatching `onBatchComplete` any less with TurboModules on than we do with TurboModules off.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D20831546
fbshipit-source-id: b2eb4e0097e0dabf8c4bd8fdc4c850a0858af699
Summary:
Might be worthwhile to just kill this method instead, since we're having all NativeModules provide their TurboModule jsi::HostObjects. But I'll leave that decision to a later time.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D20809201
fbshipit-source-id: ee73d4b5454a76460832a54f9b864841e5b2b9c0
Summary:
This is necessary to integrate TurboModule async method dispatch with the bridge's `onBatchComplete` event. See D20717931 for more details.
This diff is similar to D20480971.
**Note:** This stack doesn't really make any functional changes, since the native CallInvoker is `nullptr` right now.
Changelog:
[Internal]
Reviewed By: fkgozali
Differential Revision: D20809199
fbshipit-source-id: bf465a3a51bdddb8b56d1e696ca510fdf071f9ec
Summary:
## Context
For now, assume TurboModules doesn't exist.
**What happens when we call an async NativeModule method?**
Everytime JS calls an async NativeModule method, we don't immediately execute it. The legacy infra pushes the call into some queue managed by `MessageQueue.js`. This queue is "flushed" or "emptied" by the following events:
- **Flushed:** A C++ -> JS call. NativeModule async methods can called with an `onSuccess` and/or `onFail` callback(s). Calling `NativeToJsBridge::invokeCallback` to invoke one of these callbacks is one way for ObjC++/C++/Java to call into JS. Another way is via JSModule method calls, which are initiated by `NativeToJsBridge::callFunction`.
- **Flushed:** When `JSIExecutor::flush` is called. Since TurboModules don't exist, this only happens when we call `JSIExecutor::loadApplicationScript`.
- **Emptied:** When more than 5 ms have passed, and the queue hasn't been flushed/emptied, on the next async NativeModule method call, we add to the queue. Afterwards, we empty it, and invoke all the NativeModule method calls.
**So, what's the difference between flushed and emptied?**
> Note: These are two terms I just made up, but the distinction is important.
If the queue was "flushed", and it contained at least one NativeModule method call, `JsToNativeBridge` dispatches the `onBatchComplete` event. On Android, the UIManager module is the only module that listens to this event. This `onBatchComplete` event doesn't fire if the queue was "emptied".
**Why does any of this matter?**
1. TurboModules exist.
2. We need the TurboModules infra to have `JsToNativeBridge` dispatch `onBatchComplete`, which depends on:
- **Problem 1:** The queue being flushed on calls into JS from Java/C++/ObjC++.
- **Problem 2:** There being queued up NativeModule async method calls when the queue is flushed.
In D14656466, fkgozali fixed Problem 1 by making every C++/Java/Obj -> JS call from TurboModules also execute `JSIExecutor::flush()`. This means that, with TurboModules, we flush the NativeModule async method call queue as often as we do without TurboModules. So far, so good. However, we still have one big problem: As we convert more NativeModules to TurboModules, the average size of the queue of NativeModule method calls will become smaller and smaller, because more NativeModule method calls will be TurboModule method calls. This queue will more often be empty than not. Therefore, we'll end up dispatching the `onBatchComplete` event less often with TurboModules enabled. So, somehow, when we're about to flush the NativeModule method call queue, we need `JsToNativeBridge` to understand that we've executed TurboModule method calls in the batch. These calls would have normally been queued, which would have led the queue size to be non-zero. So if, during a batch, some TurboModule async method calls were executed, `JsToNativeBridge` should dispatch `onBatchComplete`.
**So, what does this diff do?**
1. Make `Instance` responsible for creating the JS `CallInvoker`.
2. Make `NativeToJsBridge` responsible for creating the native `CallInvoker`. `Instance` calls into `NativeToJsBridge` to get the native `CallInvoker`.
3. Hook up `CatalystInstanceImpl`, the Android bridge, with the new JS `CallInvoker`, and the new native `CallInvoker`. This fixes `onBatchComplete` on Android. iOS work is pending.
Changelog:
[Android][Fixed] - Ensure `onBatchComplete` is dispatched correctly with TurboModules
Reviewed By: mdvacca
Differential Revision: D20717931
fbshipit-source-id: bc3ccbd6c135b7f084edbc6ddb4d1e3c0c7e0875
Summary:
`fbsource//xplat` and `//xplat` are equivalent for FB BUCK targets. Removing extra prefix for consistency.
Changelog: [Internal]
Reviewed By: scottrice
Differential Revision: D20495655
fbshipit-source-id: a57b72f694c533e2e16dffe74eccb8fdec1f55f5
Summary:
Just includes the manual changes to get the perf logger moved to the ObjCTurboModule constructor.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D20498443
fbshipit-source-id: e607588f47ccc8ae199cd97043b09dfaff7a72dd
Summary:
Previously, I had logic inside `RCTTurboModuleManager` to attach the `id<RCTTurboModulePerformanceLogger>` to the `ObjCTurboModule` object
```
/**
* By default, all TurboModules are long-lived.
* Additionally, if a TurboModule with the name `name` isn't found, then we
* trigger an assertion failure.
*/
auto turboModule = [strongSelf provideTurboModule:moduleName];
/**
* TODO(T63718299): Move this setter into the ObjCTurboModule constructor
*/
if (performanceLogger) {
if (auto objCTurboModule = std::dynamic_pointer_cast<facebook::react::ObjCTurboModule>(turboModule)) {
objCTurboModule->setRCTTurboModulePerformanceLogger(performanceLogger);
};
}
```
This diff removes that logic in `RCTTurboModuleManager`, and it also removes `ObjCTurboModule::setRCTTurboModulePerformanceLogger`. Henceforth, we'll instead pass the `id<RCTTurboModulePerformanceLogger>` into `ObjCTurboModule`'s constructor. I've made all the necessary changes to the codegen scripts in this diff as well.
This should also resolve T63903079 by simply eliminating the code that's crashing production FB apps.
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D20480971
fbshipit-source-id: c3899981f880aa5d1354b5c3f4018c8fd57c3bae
Summary:
In production, we'd like to be able to track the performance metrics of TurboModules. So, I'm introducing a new protocol called `RCTTurboModulePerformanceLogger`. It allows the application to be notified at points during stages of TurboModule creation, and TurboModule method invocation.
**Why can't we use `RCTPerformanceLogger`?**
`RCTPerformanceLogger` is meant to be used to profile RN initialization. It supports a fixed list of markers. Each marker has a type, and there can only be one instance of it. This isn't suitable for logging events in the TurboModule system.
Reviewed By: mdvacca
Differential Revision: D20310976
fbshipit-source-id: a49251b325c94f912cd35ab1cd8fc010f2cfc08f
Summary:
This can cause a deadlock if the main thread synchronously calls into some JS that creates a TurboModule. However, this is also the behaviour of legacy NativeModules. Furthermore, this also greatly simplifies how we'll fix async method invocation in TurboModules: T55255146.
Changelog:
[Internal]
Reviewed By: fkgozali
Differential Revision: D20364251
fbshipit-source-id: d0db85665506f08c51c2f33a123e69960923e7f3
Summary:
The new name is get_preprocessor_flags_for_build_mode.
Changelog: [Internal]
Reviewed By: d16r
Differential Revision: D20351718
fbshipit-source-id: 67628ce81e7244f0f72af2d00d92842a649ff619
Summary:
Updating react-native-windows to build with Visual Studio 2019 and the v142 toolset.
## Changelog
[Internal] [Fixed] - Fix MSVC build break in VS 2019
Pull Request resolved: https://github.com/facebook/react-native/pull/28253
Test Plan: No real change, only adding a missing include for `stdexcept`.
Reviewed By: cpojer
Differential Revision: D20321273
Pulled By: shergin
fbshipit-source-id: ffe8b4d3a96ce4ed1db303020d77a806637e88bc
Summary:
Adding a `.clang-tidy` to a bunch of dirs under `react-native-github/ReactAndroid` and `react-native-github/ReactCommon`.
I don't want to add a single `.clang-tidy` at the root because we'll need more fine-grained control over what checks are enabled in different parts of the codebase; for example, fabric will and TM will probably have more checks enabled than older parts of the codebase that we're not actively modernizing, and the Hermes team probably wants its own config to be consistent with the rest of their codebase.
Starting off each `.clang-tidy` by only enabling clang-diagnostic; this is just to test that it's working. In the future, we'll work with the community to gradually enable more checks.
Changelog: [Internal]
Reviewed By: mdvacca
Differential Revision: D19705749
fbshipit-source-id: 979cea053b645ac4a9790340033bfcfb49ca0f97
Summary:
After D19565499, the `LongLivedObjectCollection` will be cleared on the JS thread when the jsi::Runtime is deleted. This diff makes it so that we never hold strong references to `CallbackWrapper`s in our Android TurboModules infra. Therefore, we can leverage the changes in D19565499 to ensure that our `jsi::Function`s are deleted before the `jsi::Runtime`.
## Caveat
If you delete a TurboModule by itself, it's jsi::Functions that haven't been invoked won't be released. This is also the case for iOS. I plan to fix this for both iOS and Android at a later point in time.
Changelog:
[Android][Fixed] - Refactor jsi::Function cleanup in TurboModules
Reviewed By: mdvacca
Differential Revision: D19589151
fbshipit-source-id: efa3cc6c83634014159ac7500dcf6bef9c925762
Summary:
## Description
You're not supposed to hold on to JSI objects (ex: `jsi::Function`) past the point where their `jsi::Runtime` is deleted. Otherwise, we get a dangling pointer crash, like this: T60262810! Historically, this cleanup problem has always been really tricky to get right. With this diff, I hope to fix that problem once and for all by deleting all `jsi::Function`s when we delete the global `__turboModuleProxy` function.
## Current Setup
- The TurboModules infra uses weak references to `CallbackWrapper`s to hold on to the `jsi::Function`s passed from JS to ObjC.
- The LongLivedObjectCollection holds on to strong references to `CallbackWrapper`s. This ensures that the `jsi::Function`s aren't deleted prematurely. This also means that we can use `LongLivedObjectCollection` to delete all `CallbackWrappers`.
- `TurboModuleBinding` is the abstraction we use to install the global `__turboModuleProxy` function. It is owned by `TurboModuleManager`, and `TurboModuleManager` uses it to clear all references to `jsi::Function`s, when we delete all NativeModules.
## Solution
1. Transfer ownership of `TurboModuleBinding` from `TurboModuleManager` to the `__turboModuleProxy` function.
2. Clear the `LongLivedObjectCollection` when `TurboModuleBinding` is deleted.
Changelog:
[iOS][Fixed] - Clear all held jsi::Functions when jsi::Runtime is deleted
Reviewed By: JoshuaGross
Differential Revision: D19565499
fbshipit-source-id: e3510ea04e72f6bda363a8fc3ee2be60303b70a6