Analyzing Error Logs and Crashes Triggered by Using Node-API
The maintenance and debugging measures mentioned in this topic rely on the Ark runtime multi-thread check. Therefore, you are advised to enable this feature before debugging. For details about how to enable Ark runtime multi-thread check, see Analyzing CPP Crash.
Unless otherwise specified, the maintenance and debugging measures used in this topic will interrupt the process once the Ark runtime multi-thread check is enabled.
Inconsistent napi_env Between the Data Being Used and the Data Being Created
Logs
The inconsistency in napi_env occurs in the following scenarios:
-
The napi_env of the called Node-API is inconsistent with that used to create the Node-API struct.
Log
param env not equal to its owner. -
The napi_env of the called Node-API is consistent with that used to create the Node-API struct, but the original napi_env has been destroyed.
Log
-
For the methods other than thread-safe functions, the log information is as follows:
owner env has been destroyed, owner id: <owner id> , current env id: <current id>.
-
For thread-safe functions, the log information is as follows:
current tsfn was created by dead env, owner id: <owner id>, current env id: <current id>
-
The following APIs may trigger this error:
- napi_get_reference_value
- napi_delete_reference*
- napi_queue_async_work
- napi_queue_async_work_with_qos
- napi_cancel_async_work
- napi_call_threadsafe_function*
- napi_release_threadsafe_function*
The APIs with an asterisk (*) can only trigger the log information in the second scenario. The APIs without an asterisk (*) can trigger the log information in both scenarios.
Example
NOTE
The following code is intended only to create exception scenarios and trigger DFX error logs. Do not apply it to your code before you fully understand its purpose.
Utility Class
Define a utility class to construct the two exception scenarios.
#define CHECK(cond) \
do { \
if (cond) { \
OH_LOG_FATAL(LOG_APP, "Failed to check `" #cond "`"); \
std::abort(); \
} \
} while(0)
#define CHECK_EQ(lhs, rhs) CHECK(lhs == rhs)
#define CHECK_NE(lhs, rhs) CHECK(lhs != rhs)
#define CHECK_NOT_NULL(val) CHECK(val != nullptr)
#define STRICT_NAPI_CALL(call) \
do { \
napi_status ret = (call); \
if (ret != napi_ok) { \
OH_LOG_FATAL(LOG_APP, "Failed to execute `" #call "`, " \
"return code is: %{public}d", ret); \
std::abort(); \
} \
} while(0)
class CallbackInfo {
public:
CallbackInfo(napi_env env, napi_callback_info info)
: env_(env)
{
napi_get_cb_info(env, info, &argc_, nullptr, &thisVar_, &data_);
if (argc_ > 0) {
argv_ = new napi_value[argc_];
CHECK_NOT_NULL(argv_);
memset(argv_, nullptr, sizeof(argv_));
napi_get_cb_info(env, info, &argc_, argv_, nullptr, nullptr);
}
}
~CallbackInfo()
{
if (argc_ > 0) {
delete[] argv_;
argv_ = nullptr;
}
}
inline size_t GetArgc() const { return argc_; }
inline napi_value* GetArgs() const { return argv_; }
inline napi_value GetArg(size_t index) const
{
if (index >= argc_) {
napi_value undefined = nullptr;
napi_get_undefined(env_, &undefined);
return undefined;
}
return argv_[index];
}
inline napi_value operator[](size_t index) const
{
return GetArg(index);
}
private:
napi_env env_ { nullptr };
size_t argc_ { 0 };
napi_value* argv_ { nullptr };
napi_value thisVar_ { nullptr };
void* data_ { nullptr };
};
// Construct napi_env with the same address (or different addresses) to trigger different DFX information.
class EngineProxy {
public:
EngineProxy()
{
STRICT_NAPI_CALL(napi_create_ark_runtime(&env_));
// 5: makes easier reuse of the napi_env address.
for (int i = 0; i < 5; i++) {
RecreateOnce();
}
}
~EngineProxy()
{
STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_));
}
inline bool RecreateSame()
{
return Recreate(true);
}
inline bool RecreateDiff()
{
return Recreate(false);
}
inline operator napi_env() const
{
return env_;
}
// Re-create napi_env until the address is the same as (or different from) the original napi_env.
bool Recreate(bool requireSame)
{
const char* recreateTypeTag = requireSame ? "same" : "different";
napi_env old = env_;
for (int i = 0; i < MAX_RETRY_TIMES; i++) {
if (RecreateOnce(old) == requireSame) {
OH_LOG_INFO(LOG_APP, "Succeed to recreate env with %{public}s pointer "
"address after retried %{public}d times.", recreateTypeTag, i);
return true;
}
}
OH_LOG_ERROR(LOG_APP, "Failed to recreate env with %{public}s pointer "
"address after retried %{public}d times.", recreateTypeTag, MAX_RETRY_TIMES);
return false;
}
private:
// Re-create napi_env and return whether the new address is the same as the original address.
bool RecreateOnce(napi_env old = nullptr)
{
STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_));
STRICT_NAPI_CALL(napi_create_ark_runtime(&env_));
return env_ == old;
}
napi_env env_ {nullptr};
constexpr static int MAX_RETRY_TIMES = 1 << 8;
};
napi_ref APIs
Sample code of napi_get_reference_value and napi_delete_reference:
/*
* API declaration index.d.ts
* const triggerDFXGetRef: (samePtr: boolean) => void;
*/
napi_value TriggerDFXGetRef(napi_env env, napi_callback_info cbinfo)
{
CallbackInfo info(env, cbinfo);
bool same = true;
STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same));
std::thread([](bool same) {
EngineProxy localEnv;
napi_value obj = nullptr;
STRICT_NAPI_CALL(napi_create_object(localEnv, &obj));
napi_ref ref = nullptr;
// napi_create_reference creates a strong reference for the JS object. You need to use napi_delete_reference to destroy the reference. Otherwise, the JS object cannot be reclaimed, causing memory leaks.
napi_create_reference(localEnv, obj, 1, &ref);
if (!localEnv.Recreate(same)) {
if (ref != nullptr) {
napi_delete_reference(localEnv, ref);
}
return;
}
napi_value result = nullptr;
napi_get_reference_value(localEnv, ref, &result);
if (ref != nullptr) {
napi_delete_reference(localEnv, ref);
}
}, same).detach();
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXDelRef: () => void;
*/
napi_value TriggerDFXDelRef(napi_env, napi_callback_info info)
{
std::thread([]() {
EngineProxy localEnv;
napi_value obj = nullptr;
STRICT_NAPI_CALL(napi_create_object(localEnv, &obj));
napi_ref ref = nullptr;
// Call napi_delete_reference to destroy the reference to avoid memory leaks.
napi_create_reference(localEnv, obj, 1, &ref);
if (!localEnv.RecreateSame()) {
if (ref != nullptr) {
napi_delete_reference(localEnv, ref);
}
return;
};
if (ref != nullptr) {
napi_delete_reference(localEnv, ref);
}
}).detach();
return nullptr;
}
napi_async_work APIs
Sample code of napi_queue_async_work, napi_queue_async_work_with_qos, and napi_cancel_async_work:
/*
* The macro EXPAND_ASYNC_WORK_CASE provides the following variables for op:
* @variable napi_env localEnv
* @variable napi_async_work work
*/
#define EXPAND_ASYNC_WORK_CASE(name, op) \
napi_value name(napi_env env, napi_callback_info cbinfo) \
{ \
CallbackInfo info(env, cbinfo); \
bool same = true; \
STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same)); \
std::thread([](bool same) { \
EngineProxy localEnv; \
napi_async_work work = nullptr; \
{ \
napi_value taskName = nullptr; \
napi_create_string_utf8(localEnv, #name, NAPI_AUTO_LENGTH, &taskName); \
/* Do not use an empty execute callback to create napi_async_work. */ \
/* Memory leak may occur here, which is only for DFX maintenance and debugging. */ \
napi_create_async_work(localEnv, nullptr, taskName, \
[](napi_env, void*) {}, [](napi_env, napi_status, void* ) {}, \
nullptr, &work); \
if (!localEnv.Recreate(same)) { \
if (work != nullptr) { \
napi_delete_async_work(localEnv, work); \
} \
return; \
} \
} \
(op); \
if (work != nullptr) { \
napi_delete_async_work(localEnv, work); \
} \
}, same).detach(); \
return nullptr; \
}
/*
* API declaration index.d.ts
* const triggerDFXQueueWork: (samePtr: boolean) => void;
* const triggerDFXQueueWorkWithQos: (samePtr: boolean) => void;
* const triggerDFXCancelWork: (samePtr: boolean) => void;
*/
EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWork,
napi_queue_async_work(localEnv, work))
EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWorkWithQos,
napi_queue_async_work_with_qos(localEnv, work, napi_qos_default))
EXPAND_ASYNC_WORK_CASE(TriggerDFXCancelWork,
napi_cancel_async_work(localEnv, work))
#undef EXPAND_ASYNC_WORK_CASE
napi_threadsafe_function APIs
Sample code of napi_call_threadsafe_function and napi_release_threadsafe_function:
/*
* The macro EXPAND_THREADSAFE_FUNCTION_CASE provides the following variables for op:
* @variable napi_env localEnv
* @variable napi_threadsafe_function tsfn
*/
#define EXPAND_THREADSAFE_FUNCTION_CASE(name, op) \
napi_value name(napi_env, napi_callback_info info) { \
std::thread([]() { \
EngineProxy localEnv; \
napi_threadsafe_function tsfn = nullptr; \
{ \
napi_value taskName = nullptr; \
napi_create_string_utf8(localEnv, "Test", NAPI_AUTO_LENGTH, &taskName); \
// napi_create_threadsafe_function creates a thread-safe function. \
// After the task is complete, call napi_release_threadsafe_function to release it.
napi_create_threadsafe_function( \
localEnv, nullptr, nullptr, taskName, 0, 1, nullptr, \
[](napi_env, void *, void *) {}, nullptr, \
[](napi_env, napi_value, void *, void *) {}, &tsfn); \
if (status != napi_ok) { \
OH_INFO_ERROR(LOG_APP,"napi_create_threadsafe_function failed"); \
return nullptr; \
} \
if (!localEnv.RecreateSame()) { \
return; \
}; \
} \
(op); \
}).detach(); \
return nullptr; \
}
/*
* API declaration index.d.ts
* const triggerDFXTsfnCall: () => void;
* const triggerDFXTsfnRelease: () => void;
*/
EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnCall,
napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_nonblocking))
EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnRelease,
napi_release_threadsafe_function(tsfn, napi_tsfn_release))
#undef EXPAND_THREADSAFE_FUNCTION_CASE
Cross-Thread Calls
Logs
Most Node-API interfaces are not thread-safe. Additional measures are used to locate the errors caused by improper use of Node-API interfaces.
Unless otherwise specified, the maintenance and debugging measures used in this topic will interrupt the process once the Ark runtime multi-thread check is enabled.
Log
current napi interface cannot run in multi-thread, thread id: <env tid>, current thread id: <current tid>
The following APIs may trigger this type of error:
- napi_add_env_cleanup_hook*
- napi_remove_env_cleanup_hook*
- napi_add_async_cleanup_hook
- napi_set_instance_data
- napi_get_instance_data
When the triggering conditions are met in the debugging process, the APIs with an asterisk (*) can print the ERROR log with call stack information instead of interrupting the process.
Example
NOTE
The following code is intended only to create exception scenarios and trigger DFX error logs. Do not apply it to your code before you fully understand its purpose.
env_cleanup_hook APIs
Sample code of napi_add_env_cleanup_hook and napi_remove_env_cleanup_hook:
static void EnvCLeanUpCallback(void *arg) {
char* data = reinterpret_cast<char *>(arg);
delete data;
}
/*
* API declaration index.d.ts
* const triggerDFXClnAddXT: () => void;
*/
napi_value TriggerDFXClnAddXT(napi_env env, napi_callback_info info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
std::thread([](napi_env env, char* data) {
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
}, env, data).join();
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
delete data;
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXClnAddMT: () => void;
*/
napi_value TriggerDFXClnAddMT(napi_env env, napi_callback_info info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
delete data;
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXClnRmXT: () => void;
*/
napi_value TriggerDFXClnRmXT(napi_env env, napi_callback_info info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
std::thread([](napi_env env, char* data) {
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
delete data;
}, env, data).join();
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXClnRmMT: () => void;
*/
napi_value TriggerDFXClnRmMT(napi_env env, napi_callback_info info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
// Ensure consistency in parameters used for registering and deregistering cleanup hooks. It is more important than the errors caused by repeated deregistration.
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data));
delete data;
return nullptr;
}
async_cleanup_hook APIs
Sample code of napi_add_async_cleanup_hook:
static void AsyncCleanupCallback(napi_async_cleanup_hook_handle handle, void *)
{
napi_remove_async_cleanup_hook(handle);
}
/*
* API declaration index.d.ts
* const triggerDFXAsyncAddXT: () => void;
*/
napi_value TriggerDFXAsyncAddXT(napi_env env, napi_callback_info info)
{
std::thread([](napi_env env) {
napi_add_async_cleanup_hook(env, AsyncCleanupCallback, nullptr, nullptr);
}, env).join();
return nullptr;
}
instance_data APIs
Sample code of napi_set_instance_data and napi_get_instance_data:
/*
* API declaration index.d.ts
* const triggerDFXInsSetXT: () => void;
*/
napi_value TriggerDFXInsSetXT(napi_env env, napi_callback_info info)
{
std::thread([](napi_env env) {
napi_set_instance_data(env, nullptr, [](napi_env, void *, void *) {}, nullptr);
}, env).join();
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXInsGetXT: () => void;
*/
napi_value TriggerDFXInsGetXT(napi_env env, napi_callback_info info)
{
std::thread([](napi_env env) {
void *data = nullptr;
napi_get_instance_data(env, &data);
}, env).join();
return nullptr;
}