-
Notifications
You must be signed in to change notification settings - Fork 25k
Description
Description
While playing with the JSI I have found that using Promises in conjunction with async JSI functions can lead to the JS thread being blocked. According to the react native profiler the JS thread is still operating at 60fps but the JS thread is not responding to tap handlers and never resolves the promise. Note this happens sometimes and is hard to predict which suggests there is some race condition.
I am able to unblock the JS thread by periodically kicking the event loop into action with something like
const tick = async () => {
await new Promise(r => setTimeout(r, 1000))
tick()
}
tick()I've also noticed that using Promises in conjunction with the JSI can occasionally lead to extremely long await times for the promise to be resolved, in the order of 250ms.
I have only tested this on iOS without Hermes. Occurs on both simulator and device.
Version
0.67.1
Output of npx react-native info
System:
OS: macOS 12.0.1
CPU: (10) x64 Apple M1 Max
Memory: 20.13 MB / 32.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 14.18.2 - ~/.nvm/versions/node/v14.18.2/bin/node
Yarn: 1.22.17 - ~/.nvm/versions/node/v14.18.2/bin/yarn
npm: 6.14.6 - ~/.nvm/versions/node/v14.18.2/bin/npm
Watchman: 2021.12.20.00 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.11.2 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 21.0.1, iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Xcode: 13.1/13A1030d - /usr/bin/xcodebuild
Languages:
Java: Not Found
Python: 2.7.18 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: 16.13.1 => 16.13.1
react-native: 0.63.4 => 0.63.4
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found
Steps to reproduce
A minimal failing example can be found here https://github.com/mfbx9da4/react-native-jsi-promise
1
Create an async native JSI function which spawns a thread, does some work and then resolves once it's done its work. You can use a callback or a promise to resolve from the native function. I've opted to use a callback as there are less moving parts this way. You could also use std::thread instead of GCD's dispatch_async for spawning the thread, the behaviour is observed with both. I've opted to use dispatch_async below.
auto foo = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "foo"),
1,
[cryptoPp, myqueue](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
auto userCallbackRef = std::make_shared<jsi::Object>(arguments[0].getObject(runtime));
dispatch_async(myqueue, ^(void){
auto val = jsi::String::createFromUtf8(runtime, std::to_string(std::rand()));
auto error = jsi::Value::undefined();
userCallbackRef->asFunction(runtime).call(runtime, error, val);
});
return jsi::Value::undefined();
}
);
jsiRuntime.global().setProperty(jsiRuntime, "foo", std::move(foo));2
Call the JSI function in a loop resolving the promise
for (let i = 0; i < 10; i++) {
const start = Date.now();
await new Promise(r => {
jsiPromise.foo((err, x) => {
console.log(i, 'err, x', err, x, Date.now() - start);
r(x);
});
});
}It might take several refreshes to get stuck but the JS thread does eventually get stuck.
Snack, code example, screenshot, or link to a repository
https://github.com/mfbx9da4/react-native-jsi-promise