Skip to content

Async JSI functions with promises block the event loop indefinitely  #33006

@mfbx9da4

Description

@mfbx9da4

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));

Source code here

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);
      });
    });
  }

Source code here

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

Metadata

Metadata

Assignees

Labels

StaleThere has been a lack of activity on this issue and it may be closed soon.Type: New ArchitectureIssues and PRs related to new architecture (Fabric/Turbo Modules)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions