Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling JS callback asynchronously from C++ thread. #44813

Open
AbeereSpark opened this issue Jun 6, 2024 · 6 comments
Open

Calling JS callback asynchronously from C++ thread. #44813

AbeereSpark opened this issue Jun 6, 2024 · 6 comments
Labels
Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Newer Patch Available Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)

Comments

@AbeereSpark
Copy link

AbeereSpark commented Jun 6, 2024

Description

I am encountering an issue where my React Native app crashes when a JavaScript callback is called from C++ code running on a C++ thread asynchronously. However, when I call the same callback on the same thread from where the foo function is called, it works as expected without crashing.

Steps to reproduce

Nil

React Native Version

0.74.1

Affected Platforms

Runtime - Android, Build - Windows

Areas

TurboModule - The New Native Module System, JSI - Javascript Interface, Bridgeless - The New Initialization Flow

Output of npx react-native info

System:
  OS: Windows 10 10.0.19045
  CPU: (4) x64 Intel(R) Core(TM) i5-6500T CPU @ 2.50GHz
  Memory: 6.54 GB / 15.88 GB
Binaries:
  Node:
    version: 20.13.0
    path: E:\Program Files\nodejs\node.EXE
  Yarn:
    version: 3.6.1
    path: ~\AppData\Roaming\npm\yarn.CMD
  npm:
    version: 10.7.0
    path: E:\Program Files\nodejs\npm.CMD
  Watchman: Not Found
SDKs:
  Android SDK:
    API Levels:
      - "29"
      - "30"
      - "31"
      - "34"
    Build Tools:
      - 30.0.2
      - 30.0.3
      - 33.0.1
      - 34.0.0
    System Images:
      - android-30 | Intel x86_64 Atom
      - android-30 | Google Play Intel x86 Atom
      - android-30 | Google APIs ATD Intel x86_64 Atom
      - android-34 | Google APIs Intel x86_64 Atom
    Android NDK: Not Found
  Windows SDK: Not Found
IDEs:
  Android Studio: Not Found
  Visual Studio: Not Found
Languages:
  Java: 17.0.11
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.74.1
    wanted: 0.74.1
  react-native-windows: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: false
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Stacktrace or Logs

06-06 09:22:40.260 21837 21859 W libEGL  : EGLNativeWindowType 0xb40000786029bbc0 disconnect failed                     06-06 09:22:40.313 21837 22216 I MyApp   : Log message to Android                                                       06-06 09:22:40.367 21837 22216 I ReactNativeJS: 'error:', undefined, 'result:', 'hello world'                           06-06 09:22:40.369 21837 22216 E libc++abi: terminating due to uncaught exception of type facebook::jsi::JSError: Can't find variable: setHello                                                                                                 06-06 09:22:40.369 21837 22216 E libc++abi:                                                                             06-06 09:22:40.369 21837 22216 E libc++abi: callback                                                                    06-06 09:22:40.369 21837 22216 E libc++abi: onFastRefresh@http://localhost:8081/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.bikegadgetmainappexample&modulesOnly=false&runModule=true:44263:35                        06-06 09:22:40.369 21837 22216 E libc++abi: performReactRefresh@http://localhost:8081/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.bikegadgetmainappexample&modulesOnly=false&runModule=true:62080:34                  06-06 09:22:40.369 21837 22216 E libc++abi: http://localhost:8081/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.bikegadgetmainappexample&modulesOnly=false&runModule=true:435:40                                        --------- beginning of crash                                                                                            06-06 09:22:40.370 21837 22216 F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 22216 (mqt_v_js), pid 21837 (tmainappexample)                                                                                                    06-06 09:22:40.472 22219 22219 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstone           06-06 09:22:40.473   249   249 I tombstoned: received crash request for pid 22216                                       06-06 09:22:40.481 22219 22219 I crash_dump64: performing dump of process 21837 (target tid = 22216)                    06-06 09:22:40.497 22219 22219 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***              06-06 09:22:40.498 22219 22219 F DEBUG   : Build fingerprint: 'Firefly/rk356x/rk356x:11/RD2A.211001.002/lwy07101153:userdebug/release-keys'                                                                                                     06-06 09:22:40.498 22219 22219 F DEBUG   : Revision: '0'                                                                06-06 09:22:40.498 22219 22219 F DEBUG   : ABI: 'arm64'                                                                 06-06 09:22:40.499 22219 22219 F DEBUG   : Timestamp: 2024-06-06 09:22:40+0000                                          06-06 09:22:40.499 22219 22219 F DEBUG   : pid: 21837, tid: 22216, name: mqt_v_js  >>> com.bikegadgetmainappexample <<< 06-06 09:22:40.499 22219 22219 F DEBUG   : uid: 10120                                                                   06-06 09:22:40.499 22219 22219 F DEBUG   : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------                  06-06 09:22:40.499 22219 22219 F DEBUG   : Abort message: 'terminating due to uncaught exception of type facebook::jsi::JSError: Can't find variable: setHello

Reproducer

JSI-App

Screenshots and Videos

App.tsx

import * as React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { multiply, install} from 'react-native-bikegadget-mainapp';

const callback = (error, result) => {
  console.log('error:', error, 'result:', result);
  setHello("message from js");
}

export default function App() {
  const [result, setResult] = React.useState(multiply(4, 6));
  const [hello, setHello] = React.useState('');

  React.useEffect(() => {
    install();
    setHello(helloWorld());
    foo(callback);
    
    // callback("err123", "hello")
  }, []);

  return (
    <View style={styles.container}>
      <Text>Result: {result}</Text>
      <Text>Hello: {hello}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 60,
    height: 60,
    marginVertical: 20,
  },
});

C++ module

#include "react-native-bikegadget-mainapp.h"
#include <jsi/jsi.h>
#include <jsi/jsilib.h>
#include <thread>
#include <android/log.h>
#include <ReactCommon/CallInvoker.h>

using namespace facebook::jsi;
using namespace std;

namespace bikegadgetmainapp
{
	// Function to register helloWorld in JSI
	void registerHelloWorld(Runtime &jsiRuntime)
	{
		auto helloWorld = Function::createFromHostFunction(
			jsiRuntime,
			PropNameID::forAscii(jsiRuntime, "helloWorld"),
			0,
			[](Runtime &runtime,
			   const Value &thisValue,
			   const Value *arguments,
			   size_t count) -> Value
			{
				string helloworld = "helloworld Ash 123";
				return Value(runtime, String::createFromUtf8(runtime, helloworld));
			});

		jsiRuntime.global().setProperty(jsiRuntime, "helloWorld", std::move(helloWorld));
	}

	void registerFoo(Runtime &jsiRuntime)
	{
		auto foo = Function::createFromHostFunction(
			jsiRuntime,
			PropNameID::forAscii(jsiRuntime, "foo"),
			1,
			[](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value
			{
				if (count < 1 || !arguments[0].isObject() || !arguments[0].getObject(runtime).isFunction(runtime))
				{
					throw JSError(runtime, "Expected a function as the first argument");
				}

				// Create a shared reference to the user callback
				auto userCallbackRef = std::make_shared<Object>(arguments[0].getObject(runtime));

				// Lambda to be run in the new thread
				auto f = [&runtime, userCallbackRef]()
				{
					auto val = std::make_shared<std::string>("hello world");
					auto error = std::make_shared<Value>(Value::undefined());

					// Periodically log messages to Android log and sleep for 5 seconds
					while (true)
					{
						__android_log_print(ANDROID_LOG_INFO, "MyApp", "Log message to Android");
                        userCallbackRef->asFunction(runtime).call(runtime, *error, *val);
						std::this_thread::sleep_for(std::chrono::seconds(5));
					}
				};

				// // Launch the lambda in a new thread
				std::thread thread_object(f);
				thread_object.detach();

				return Value::undefined();
			});

		jsiRuntime.global().setProperty(jsiRuntime, "foo", std::move(foo));
	}
	// Function to install the JSI bindings
	void install(Runtime &jsiRuntime)
	{
		// Register the helloWorld function
		registerHelloWorld(jsiRuntime);
		registerFoo(jsiRuntime);
	}

	double multiply(double a, double b)
	{
		return a * b;
	}
}
@AbeereSpark AbeereSpark added Needs: Triage 🔍 Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules) labels Jun 6, 2024
Copy link

github-actions bot commented Jun 6, 2024

⚠️ Newer Version of React Native is Available!
ℹ️ You are on a supported minor version, but it looks like there's a newer patch available - 0.74.2. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.

@github-actions github-actions bot added Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. labels Jun 6, 2024
@cortinico
Copy link
Contributor

Please create a repro using the Reproducer Template

@github-actions github-actions bot removed the Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. label Jun 6, 2024
@AbeereSpark
Copy link
Author

@cortinico I have updated the issue with the required ReproducerApp.

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback labels Jun 6, 2024
@cortinico cortinico added Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. and removed Needs: Attention Issues where the author has responded to feedback. labels Jun 17, 2024
@huzhanbo1996
Copy link

huzhanbo1996 commented Jun 20, 2024

In jsi.h, it specifies that JSRuntime can not be accessed concurrently. This crash is expected.

Represents a JS runtime. Movable, but not copyable. Note that
this object may not be thread-aware, but cannot be used safely from
multiple threads at once. The application is responsible for
ensuring that it is used safely. This could mean using the
Runtime from a single thread, using a mutex, doing all work on a
serial queue, etc. This restriction applies to the methods of
this class, and any method in the API which take a Runtime& as an
argument. Destructors (all but ~Scope), operators, or other methods
which do not take Runtime& as an argument are safe to call from any
thread, but it is still forbidden to make write operations on a single
instance of any class from more than one thread. In addition, to
make shutdown safe, destruction of objects associated with the Runtime
must be destroyed before the Runtime is destroyed, or from the
destructor of a managed HostObject or HostFunction. Informally, this
means that the main source of unsafe behavior is to hold a jsi object
in a non-Runtime-managed object, and not clean it up before the Runtime
is shut down. If your lifecycle is such that avoiding this is hard,
you will probably need to do use your own locks.
class JSI_EXPORT Runtime {}

@AbeereSpark
Copy link
Author

@huzhanbo1996 Suppose only one thread in C++ is accessing the JS Runtime then how can we stop JavaScript from using Runtime when C++ is using it?

@huzhanbo1996
Copy link

huzhanbo1996 commented Jun 28, 2024

@huzhanbo1996 Suppose only one thread in C++ is accessing the JS Runtime then how can we stop JavaScript from using Runtime when C++ is using it?

In jsi.h, it suggests following solutions.

this could mean using the
Runtime from a single thread, using a mutex, doing all work on a
serial queue, etc.

It depends on what you want to achieve. In code you post. I presume you want to dispatch the task in another thread and return the result by callback in JS Thread. You could try things like serial queue or ReactContext.runOnJSQueueThread, both need some extra development and beyond this issue's scope. I suggest that you could investigate into more open source C++ RN modules and learn their design/implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue: Author Provided Repro This issue can be reproduced in Snack or an attached project. Newer Patch Available Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)
Projects
None yet
Development

No branches or pull requests

3 participants