# 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](https://developer.huawei.com/consumer/en/doc/harmonyos-guides/ide-multi-thread-check).
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:
1. When Node-API is called to use a created struct, the **napi_env** of the struct is inconsistent with that used to create the struct.
> **Log**
> param env not equal to its owner.
>
2. When Node-API is called to use a created struct, the **napi_env** of the struct has the same address as the **napi_env** used to create the 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:
1. napi_get_reference_value
2. napi_delete_reference*
3. napi_queue_async_work
4. napi_queue_async_work_with_qos
5. napi_cancel_async_work
6. napi_call_threadsafe_function*
7. napi_release_threadsafe_function*
> **NOTE**
The APIs with an asterisk (*) can only trigger the log information in the second scenario.
### 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.
```cpp
#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**:
```cpp
/*
* 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(localEnv, obj, 1, &ref);
if (!localEnv.Recreate(same)) {
return;
};
napi_value result = nullptr;
napi_get_reference_value(localEnv, ref, &result);
}, same).detach();
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXDelRef: () => void;
*/
napi_value TriggerDFXDelRef(napi_env, napi_callback_info)
{
std::thread([]() {
EngineProxy localEnv;
napi_value obj = nullptr;
STRICT_NAPI_CALL(napi_create_object(localEnv, &obj));
napi_ref ref = nullptr;
napi_create_reference(localEnv, obj, 1, &ref);
if (!localEnv.RecreateSame()) {
return;
};
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**:
```cpp
/*
* 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. */ \
napi_create_async_work(localEnv, nullptr, taskName, \
[](napi_env, void*) {}, [](napi_env, napi_status, void* ) {}, \
nullptr, &work); \
if (!localEnv.Recreate(same)) { \
return; \
} \
} \
(op); \
}, 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**:
```cpp
/*
* 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) { \
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( \
localEnv, nullptr, nullptr, taskName, 0, 1, nullptr, \
[](napi_env, void *, void *) {}, nullptr, \
[](napi_env, napi_value, void *, void *) {}, &tsfn); \
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:
1. napi_add_env_cleanup_hook*
2. napi_remove_env_cleanup_hook*
3. napi_add_async_cleanup_hook
4. napi_set_instance_data
5. napi_get_instance_data
> **NOTE**
>
> 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**:
```cpp
static void EnvCLeanUpCallback(void *arg) {
char* data = reinterpret_cast(arg);
delete data;
}
/*
* API declaration index.d.ts
* const triggerDFXClnAddXT: () => void;
*/
napi_value TriggerDFXClnAddXT(napi_env env, napi_callback_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(data));
}, env, data).join();
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
delete data;
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXClnAddMT: () => void;
*/
napi_value TriggerDFXClnAddMT(napi_env env, napi_callback_info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
delete data;
return nullptr;
}
/*
* API declaration index.d.ts
* const triggerDFXClnRmXT: () => void;
*/
napi_value TriggerDFXClnRmXT(napi_env env, napi_callback_info)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
std::thread([](napi_env env, char* data) {
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(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)
{
char* data = new char;
CHECK_NOT_NULL(data);
*data = nullptr;
napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(data));
napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast(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(data));
delete data;
return nullptr;
}
```
#### async_cleanup_hook APIs
Sample code of **napi_add_async_cleanup_hook**:
```cpp
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)
{
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**:
```cpp
/*
* API declaration index.d.ts
* const triggerDFXInsSetXT: () => void;
*/
napi_value TriggerDFXInsSetXT(napi_env env, napi_callback_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)
{
std::thread([](napi_env env) {
void *data = nullptr;
napi_get_instance_data(env, &data);
}, env).join();
return nullptr;
}
```