-----原始邮件-----
发件人:"谭小凡" <xiaofan@iscas.ac.cn>
发送时间:2021-07-26 11:30:35 (星期一)
收件人: dev@openharmony.io
抄送:
主题: [Dev] 标准系统开发n-api的JS模块的一些问题以及解决方法
存在的问题
问题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) return nullptr; 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) return nullptr; 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) return nullptr; 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) return nullptr; 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) return nullptr; 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 = nullptr bool 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)) return nullptr; 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<GetProcessInfoAsyncInfo*>(data); async_callback_info->process_info = async_callback_info->ability->GetProcessInfo(); }, [](napi_env env, napi_status status, void *data) { GetProcessInfoAsyncInfo *async_callback_info = reinterpret_cast<GetProcessInfoAsyncInfo*>(data); std::unique_ptr<GetProcessInfoAsyncInfo> cleaner { async_callback_info }; ProcessInfo &process_info = *async_callback_info->process_info; napi_value result; napi_value pid, name; NAPI_CALL_RETURN_VOID(env, napi_create_object(env, &result)); NAPI_CALL_RETURN_VOID(env, util_napi_value_from_string_utf8(env, process_info.GetProcessName(), &name)); NAPI_CALL_RETURN_VOID(env, napi_create_uint32(env, (uint32_t)process_info.GetPid(), &pid)); NAPI_CALL_RETURN_VOID(env, napi_set_named_property(env, result, "processName", name)); NAPI_CALL_RETURN_VOID(env, napi_set_named_property(env, result, "pid", pid)); async_callback_info->notify(result); // 如果此处有失败分支的话,令第三个参数为false即可。 }, reinterpret_cast<void*>(async_callback_info), &async_callback_info->async_work)); NAPI_CALL(env, napi_queue_async_work(env, async_callback_info->async_work)); cleaner.release(); return retval; }
这样一来,以上代码同时支持callback模式和promise模式
vm = this // callback风格: 只传一个success_cb getProcessInfo(info => { vm.message = 'name: ' + info.processName }) // callback风格: 传两个回调函数 getProcessInfo(info => { vm.message = 'name: ' + info.processName }, err => { vm.message = 'fail: ' + err.message }) // callback风格: 传入一个携带success,fail,complete信息的对象 getProcessInfo({ success: info => { vm.message = 'name: ' + info.processName }, fail: err => { vm.message = 'fail: ' + err.message }, complete: () => { console.log('complete') } }) // callback风格: 传入一个不完整的回调对象 getProcessInfo({ success: info => { vm.message = 'name: ' + info.processName }, }) // promise风格 getProcessInfo() .then(info => { vm.message = info.processName }) .catch(err => { vm.message = 'fail: ' + err.message })