存在的问题
问题1. NAPI_CALL一族函数可能会导致函数体返回,造成内存泄露
(2021/7/25更新)该状态已经得到缓解,目前代码中看到的是在外层函数中申请资源,调用Wrap函数去实际干活。Wrap函数中随意使用NAPI_CALL宏,一旦异常返回,外层函数负责回收申请到的资源。
但是这种机制未解决了所有内存泄露的问题,以NAPI_GetProcessName函数为例(为了板书简短,下面代码做了适当精简)
napi_value NAPI_GetProcessName(napi_env env, napi_callback_info info) {
ProcessNameCB *processNameCB = CreateProcessNameCBInfo(env); if (processNameCB == nullptr) returnnullptr;
napi_value ret = GetProcessNameWrap(env, info, processNameCB); if (ret == nullptr && processNameCB != nullptr) { delete processNameCB;
processNameCB = nullptr;
} return ret;
}
napi_value GetProcessNameWrap(napi_env env, napi_callback_info info, ProcessNameCB *processNameCB) { if (processNameCB == nullptr) returnnullptr;
size_t argcAsync = 1; const size_t argcPromise = 0; const size_t argCountWithAsync = argcPromise + ARGS_ASYNC_COUNT;
napi_value args[ARGS_MAX_COUNT] = {nullptr};
napi_value ret = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argcAsync, args, nullptr, nullptr)); if (argcAsync > argCountWithAsync || argcAsync > ARGS_MAX_COUNT) returnnullptr; if (argcAsync > argcPromise)
ret = GetProcessNameAsync(env, args, argcAsync, argcPromise, processNameCB); else ret = GetProcessNamePromise(env, processNameCB); return ret;
}
napi_value GetProcessNameAsync(
napi_env env, napi_value *args, size_t argcAsync, const size_t argcPromise, ProcessNameCB *processNameCB) { if (args == nullptr || processNameCB == nullptr) returnnullptr;
napi_value resourceName = nullptr;
NAPI_CALL(env, napi_create_string_latin1(env, __func__, NAPI_AUTO_LENGTH, &resourceName));
napi_valuetype valuetype = napi_undefined;
NAPI_CALL(env, napi_typeof(env, args[argcPromise], &valuetype)); if (valuetype == napi_function)
NAPI_CALL(env, napi_create_reference(env, args[argcPromise], 1, &processNameCB->cbBase.cbInfo.callback));
NAPI_CALL(env,
napi_create_async_work(env, nullptr,
resourceName,
GetProcessNameExecuteCB,
GetProcessNameAsyncCompleteCB,
(void *)processNameCB,
&processNameCB->cbBase.asyncWork));
NAPI_CALL(env, napi_queue_async_work(env, processNameCB->cbBase.asyncWork));
napi_value result = nullptr;
NAPI_CALL(env, napi_get_null(env, &result)); return result;
} void GetProcessNameAsyncCompleteCB(napi_env env, napi_status status, void *data) {
ProcessNameCB *processNameCB = (ProcessNameCB *)data;
napi_value callback = nullptr;
napi_value undefined = nullptr;
napi_value result[ARGS2] = {nullptr};
napi_value callResult = nullptr;
NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
result[PARAM0] = GetCallbackErrorValue(env, NO_ERROR);
result[PARAM1] = WrapProcessName(env, processNameCB);
NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, processNameCB->cbBase.cbInfo.callback, &callback));
NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, callback, ARGS2, &result[PARAM0], &callResult)); if (processNameCB->cbBase.cbInfo.callback != nullptr)
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, processNameCB->cbBase.cbInfo.callback));
NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, processNameCB->cbBase.asyncWork)); delete processNameCB;
processNameCB = nullptr;
}
目前仍然存在的问题
cbInfo.callback和asyncWork泄露
GetProcessNameAsync调用了napi_create_reference创建了一个processNameCB->cbBase.cbInfo.callback,
一旦后续步骤发生失败,将返回空指针到NAPI_GetProcessName
就会发生NAPI_GetProcessName仅删除了processNameCB资源,并未删除processNameCB->cbBase.cbInfo.callback。
同理napi_queue_async_work函数运行失败,也会造成processNameCB->cbBase.asyncWork这个资源泄露
NAPI_CALL_RETURN_VOID导致泄露
在async_work的complete回调中,并未采用Wrap函数这种技巧,可能导致无法顺利执行资源释放的工作。
processNameCB,cbInfo.callback,asyncWork都将有未释放的风险
对不再使用的局部变量置空是无意义的写法
delete processNameCB;
processNameCB = nullptr;
问题2. 何时使用NAPI_CALL这一族宏定义来检查n-api函数是否成功
从目前代码中来看该族函数,大部分napi函数都使用了NAPI_CALL这一族宏定义来判断状态,但还是有很多未检查的代码,例如
napi_value NAPI_KillProcessesByBundleName(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[argc];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
std::string bundleName;
ParseBundleName(env, bundleName, argv[0]); // 内部使用了NAPI_CALL但外部未检查返回值是否为空指针bool callBackMode = false; if (argc >= 2) {
napi_valuetype valuetype;
NAPI_CALL(env, napi_typeof(env, argv[1], &valuetype));
NAPI_ASSERT(env, valuetype == napi_function, "Wrong argument type. Function expected.");
callBackMode = true;
}
AsyncKillProcessCallbackInfo *async_callback_info = new (std::nothrow) AsyncKillProcessCallbackInfo{.env = env, .asyncWork = nullptr, .deferred = nullptr}; if (async_callback_info == nullptr) returnnullptr;
async_callback_info->bundleName = bundleName; if (callBackMode)
napi_create_reference(env, argv[1], 1, &async_callback_info->callback[0]); // 未检查 napi_value ret = NAPI_KillProcessesByBundleNameWrap(env, info, callBackMode, async_callback_info); if (ret == nullptr) { delete async_callback_info;
async_callback_info = nullptr;
} return ((callBackMode) ? (nullptr) : (ret));
}
napi_value NAPI_KillProcessesByBundleNameWrap(
napi_env env, napi_callback_info info, bool callBackMode, AsyncKillProcessCallbackInfo *async_callback_info) { if (callBackMode) {
napi_value resourceName;
napi_create_string_latin1(env, "NAPI_KillProcessesByBundleNameCallBack", NAPI_AUTO_LENGTH, &resourceName); // 未检查 napi_create_async_work(env, nullptr,
resourceName,
[](napi_env env, void *data) {
HILOG_INFO("killProcessesByBundleName called(CallBack Mode)...");
AsyncKillProcessCallbackInfo *async_callback_info = (AsyncKillProcessCallbackInfo *)data;
async_callback_info->result = GetAbilityManagerInstance()->KillProcess(async_callback_info->bundleName);
},
[](napi_env env, napi_status status, void *data) {
HILOG_INFO("killProcessesByBundleName compeleted(CallBack Mode)...");
AsyncKillProcessCallbackInfo *async_callback_info = (AsyncKillProcessCallbackInfo *)data;
napi_value result[2] = {0};
napi_value callback;
napi_value undefined;
napi_value callResult = 0;
result[0] = GetCallbackErrorValue(async_callback_info->env, BUSINESS_ERROR_CODE_OK); // 内部使用了NAPI_CALL但外部未检查返回值是否为空指针 napi_create_int32(async_callback_info->env, async_callback_info->result, &result[1]); // 未检查 napi_get_undefined(env, &undefined); // 未检查 napi_get_reference_value(env, async_callback_info->callback[0], &callback); // 未检查 napi_call_function(env, undefined, callback, 2, &result[0], &callResult); // 未检查if (async_callback_info->callback[0] != nullptr) {
napi_delete_reference(env, async_callback_info->callback[0]); // 未检查,但此处确实无需检查 }
napi_delete_async_work(env, async_callback_info->asyncWork); // 未检查,但此处确实无需检查delete async_callback_info;
},
(void *)async_callback_info,
&async_callback_info->asyncWork); // 未检查 NAPI_CALL(env, napi_queue_async_work(env, async_callback_info->asyncWork));
napi_value ret = 0;
NAPI_CALL(env, napi_create_int32(env, 0, &ret)); // 若此函数失败程序可能会崩溃,因为会出现double delete的问题。return ret;
} else {
napi_value resourceName;
napi_create_string_latin1(env, "NAPI_KillProcessesByBundleNamePromise", NAPI_AUTO_LENGTH, &resourceName); // 未检查 napi_deferred deferred;
napi_value promise;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
async_callback_info->deferred = deferred;
napi_create_async_work(env, nullptr,
resourceName,
[](napi_env env, void *data) {
HILOG_INFO("killProcessesByBundleName called(Promise Mode)...");
AsyncKillProcessCallbackInfo *async_callback_info = (AsyncKillProcessCallbackInfo *)data;
async_callback_info->result = GetAbilityManagerInstance()->KillProcess(async_callback_info->bundleName);
},
[](napi_env env, napi_status status, void *data) {
HILOG_INFO("killProcessesByBundleName compeleted(Promise Mode)...");
AsyncKillProcessCallbackInfo *async_callback_info = (AsyncKillProcessCallbackInfo *)data;
napi_value result;
napi_create_int32(async_callback_info->env, async_callback_info->result, &result); // 未检查 napi_resolve_deferred(async_callback_info->env, async_callback_info->deferred, result); // 未检查 napi_delete_async_work(env, async_callback_info->asyncWork); // 未检查,但此处确实无需检查delete async_callback_info;
},
(void *)async_callback_info,
&async_callback_info->asyncWork); // 未检查 napi_queue_async_work(env, async_callback_info->asyncWork); // 未检查return promise;
}
}
问题3. Promise模式和Callback模式代码混乱
promise和callback的语义不统一
void AsyncCompleteCallback(napi_env env, napi_status status, void *data)
{
AsyncPermissionCallbackInfo *asyncCallbackInfo = (AsyncPermissionCallbackInfo *)data; if (asyncCallbackInfo == nullptr) return; const size_t cbCount = 2;
napi_value callback = 0;
napi_value undefined = 0;
napi_get_undefined(env, &undefined);
napi_value result = 0;
napi_value callResult = 0;
NAPI_GetNapiValue(env, &asyncCallbackInfo->native_data, &result); if (asyncCallbackInfo->run_status) { if (asyncCallbackInfo->callback[0] != nullptr) {
napi_get_reference_value(env, asyncCallbackInfo->callback[0], &callback);
napi_call_function(env, undefined, callback, 1, &result, &callResult);
napi_delete_reference(env, asyncCallbackInfo->callback[0]);
}
} else { if (asyncCallbackInfo->callback[1] != nullptr) {
napi_value rev[2] = {nullptr};
napi_create_int32(env, asyncCallbackInfo->error_code, &rev[0]);
napi_create_int32(env, asyncCallbackInfo->run_status ? 0 : -1, &rev[1]);
napi_get_reference_value(env, asyncCallbackInfo->callback[1], &callback);
napi_call_function(env, undefined, callback, cbCount, rev, &callResult);
napi_delete_reference(env, asyncCallbackInfo->callback[1]);
}
}
napi_delete_async_work(env, asyncCallbackInfo->asyncWork); delete asyncCallbackInfo;
} void PromiseCompleteCallback(napi_env env, napi_status status, void *data)
{
AsyncPermissionCallbackInfo *asyncCallbackInfo = (AsyncPermissionCallbackInfo *)data; if (asyncCallbackInfo != nullptr) {
napi_value result = 0;
NAPI_GetNapiValue(env, &asyncCallbackInfo->native_data, &result);
napi_resolve_deferred(env, asyncCallbackInfo->deferred, result);
napi_delete_async_work(env, asyncCallbackInfo->asyncWork); delete asyncCallbackInfo;
}
}
以上述代码为例,callback分为成功回调和失败回调。那为何promise始终都用napi_resolve_deferred而不用napi_reject_deferred呢?
现存代码中callback有两种写法
传入一个或两个callback,第一个表success_cb,第二个表fail_cb
传入一个callback对象,其中携带success, fail, complete三个回调
代码过于重复
大部分同时支持promise模式和callback模式的函数拥有着高度重复的代码,严重降低代码的可维护性,代码数量快速膨胀。
解决思路
问题1,2
这两个问题的根源内存泄露,其实只要解决了内存泄露,那问题1,2就能迎刃而解了
建议为asyncCallbackInfo添加析构函数,由析构函数负责释其所管理的所有资源。
具体的来说,在析构函数中调用napi_delete_reference,api_delete_async_work等资源清理函数
建议用std::unique_ptr来管理asyncCallbackInfo指针
当函数因失败而返回,或者正常退出且需要释放asyncCallbackInfo的场合,std::unique_ptr可以自动释放资源
当函数正常结束无需释放asyncCallbackInfo的场合,调用std::unique_ptr<T>::release()接口即可取消自动释放资源的功能
问题3
为async_callback_data抽象一个共同的基类来屏蔽promise,callback的差异,实现只需要写一份代码,就可以同时实现两种模式
class AsyncWorkData
{ public: // 构造函数,需要持有一个napi_env对象 AsyncWorkData(napi_env env); // 析构函数,释放本对象管理的所有资源 ~AsyncWorkData(); // argc,argv// argc == 0: promise模式// argc == 1: callback模式// * 如果type(argv[0]) == napi_function// 则将argv作为success_cb// * 如果type(argv[0]) == napi_object 的话// 则将argv[0].success作为success_cb,argv[0].fail作为fail_cb,argv[0].complete作为complete_cb// argc == 2: callback模式// 将argv[0]作为success_cb, argv[1]作为fail_cb// retval// 若为promise模式,则使*retval = promise对象// 若为callback模式,则使*retval = nullptrbool parse_args(size_t argc, napi_value argv[], napi_value *retval); // 若为promise模式:// 根据resolved值来调用napi_resolve_deferred或napi_reject_deferred函数,result作为resolve/reject的参数。// 若为callback模式// 根据resolved值来调用complete_cb(result)或fail_cb(result)。如果存在complete_cb,则调用complete_cb()void notify(napi_value result, bool resolved = true); // 每个这个成员与参数解析无关,但他是所有async_callback_data的共同成员,所以放在基类里统一管理 napi_async_work async_work; protected:
napi_env env; private:
napi_ref success_cb;
napi_ref fail_cb;
napi_ref complete_cb;
napi_deferred deferred;
};
使用这种技巧编写这种同时支持callback和promise的应用将会非常简单,例如开发一个getProcessInfo的函数
struct GetProcessNameAsyncInfo : public AsyncWorkData
{
GetProcessInfoAsyncInfo(napi_env env);
Ability *ability;
std::shared_ptr<ProcessInfo> process_info;
}; static napi_value NAPI_getProcessInfo(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value argv[1], retval, resourceName;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
GetProcessInfoAsyncInfo *async_callback_info = new GetProcessInfoAsyncInfo { env };
std::unique_ptr<GetProcessInfoAsyncInfo> cleaner { async_callback_info }; // 如果回调参数下标是从n开始的话,可以写作parse_args(argc-n, argv+n, &retval)if (!async_callback_info->parse_args(argc, argv, &retval)) returnnullptr;
NAPI_CALL(env, util_get_ability(env, &async_callback_info->ability));
NAPI_CALL(env, napi_create_string_latin1(env, __func__, NAPI_AUTO_LENGTH, &resourceName));
NAPI_CALL(env, napi_create_async_work(
env, nullptr,
resourceName,
[](napi_env env, void *data) {
GetProcessInfoAsyncInfo *async_callback_info = reinterpret_cast