1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "request_suspend_delay.h"
17 
18 #include <uv.h>
19 
20 #include "singleton.h"
21 #ifdef SUPPORT_JSSTACK
22 #include "xpower_event_js.h"
23 #endif
24 
25 #include "background_task_manager.h"
26 #include "hitrace_meter.h"
27 #include "transient_task_log.h"
28 
29 namespace OHOS {
30 namespace BackgroundTaskMgr {
31 std::map<int32_t, std::shared_ptr<ExpiredCallback>> callbackInstances_;
32 std::mutex callbackLock_;
33 static const uint32_t REQUEST_SUSPEND_DELAY_PARAMS = 2;
34 
35 struct CallbackReceiveDataWorker {
36     napi_env env = nullptr;
37     napi_ref ref = nullptr;
38     std::shared_ptr<ExpiredCallback> callback = nullptr;
39 };
40 
CallbackInstance()41 CallbackInstance::CallbackInstance() {}
42 
~CallbackInstance()43 CallbackInstance::~CallbackInstance()
44 {
45     if (expiredCallbackInfo_.ref == nullptr) {
46         return;
47     }
48     DeleteNapiRef();
49 }
50 
UvQueueWorkDeleteRef(uv_work_t * work,int32_t status)51 void UvQueueWorkDeleteRef(uv_work_t *work, int32_t status)
52 {
53     if (work == nullptr) {
54         return;
55     }
56     CallbackReceiveDataWorker *dataWorkerData = static_cast<CallbackReceiveDataWorker *>(work->data);
57     if (dataWorkerData == nullptr) {
58         delete work;
59         work = nullptr;
60         return;
61     }
62     napi_delete_reference(dataWorkerData->env, dataWorkerData->ref);
63     delete dataWorkerData;
64     dataWorkerData = nullptr;
65     delete work;
66 }
67 
DeleteNapiRef()68 void CallbackInstance::DeleteNapiRef()
69 {
70     uv_loop_s *loop = nullptr;
71     napi_get_uv_event_loop(expiredCallbackInfo_.env, &loop);
72     if (loop == nullptr) {
73         return;
74     }
75     CallbackReceiveDataWorker *dataWorker = new (std::nothrow) CallbackReceiveDataWorker();
76     if (dataWorker == nullptr) {
77         return;
78     }
79 
80     dataWorker->env = expiredCallbackInfo_.env;
81     dataWorker->ref = expiredCallbackInfo_.ref;
82     uv_work_t *work = new (std::nothrow) uv_work_t;
83     if (work == nullptr) {
84         BGTASK_LOGE("DeleteNapiRef new work failed");
85         delete dataWorker;
86         dataWorker = nullptr;
87         return;
88     }
89     work->data = static_cast<void *>(dataWorker);
90 
91     int32_t ret = uv_queue_work(loop, work, [](uv_work_t *work) {}, UvQueueWorkDeleteRef);
92     if (ret != 0) {
93         delete dataWorker;
94         dataWorker = nullptr;
95         delete work;
96         work = nullptr;
97     }
98 }
99 
UvQueueWorkOnExpired(uv_work_t * work,int32_t status)100 void UvQueueWorkOnExpired(uv_work_t *work, int32_t status)
101 {
102     BGTASK_LOGD("OnExpired uv_work_t start");
103 
104     if (work == nullptr) {
105         BGTASK_LOGE("UvQueueWorkOnExpired work is null");
106         return;
107     }
108 
109     CallbackReceiveDataWorker *dataWorkerData = static_cast<CallbackReceiveDataWorker *>(work->data);
110     if (dataWorkerData == nullptr) {
111         BGTASK_LOGE("UvQueueWorkOnExpired dataWorkerData is null");
112         delete work;
113         work = nullptr;
114         return;
115     }
116 
117     Common::SetCallback(dataWorkerData->env, dataWorkerData->ref, Common::NapiGetNull(dataWorkerData->env));
118 
119     std::lock_guard<std::mutex> lock(callbackLock_);
120     auto findCallback = std::find_if(callbackInstances_.begin(), callbackInstances_.end(),
121         [&](const auto& callbackInstance) { return callbackInstance.second == dataWorkerData->callback; }
122     );
123     if (findCallback != callbackInstances_.end()) {
124         callbackInstances_.erase(findCallback);
125     }
126 
127     delete dataWorkerData;
128     dataWorkerData = nullptr;
129     delete work;
130 }
131 
OnExpired()132 __attribute__((no_sanitize("cfi"))) void CallbackInstance::OnExpired()
133 {
134     std::lock_guard<std::mutex> lock(callbackLock_);
135     auto findCallback = std::find_if(callbackInstances_.begin(), callbackInstances_.end(),
136         [&](const auto& callbackInstance) { return callbackInstance.second.get() == this; }
137     );
138     if (findCallback == callbackInstances_.end()) {
139         BGTASK_LOGI("expired callback is not found");
140         return;
141     }
142 
143     if (expiredCallbackInfo_.ref == nullptr) {
144         BGTASK_LOGE("expired callback unset");
145         callbackInstances_.erase(findCallback);
146         return;
147     }
148 
149     uv_loop_s *loop = nullptr;
150     napi_get_uv_event_loop(expiredCallbackInfo_.env, &loop);
151     if (loop == nullptr) {
152         BGTASK_LOGE("loop instance is nullptr");
153         callbackInstances_.erase(findCallback);
154         return;
155     }
156 
157     CallbackReceiveDataWorker *dataWorker = new (std::nothrow) CallbackReceiveDataWorker();
158     if (dataWorker == nullptr) {
159         BGTASK_LOGE("new dataWorker failed");
160         callbackInstances_.erase(findCallback);
161         return;
162     }
163 
164     dataWorker->env = expiredCallbackInfo_.env;
165     dataWorker->ref = expiredCallbackInfo_.ref;
166     dataWorker->callback = shared_from_this();
167 
168     uv_work_t *work = new (std::nothrow) uv_work_t;
169     if (work == nullptr) {
170         BGTASK_LOGW("OnExpired new work failed");
171         delete dataWorker;
172         dataWorker = nullptr;
173         callbackInstances_.erase(findCallback);
174         return;
175     }
176 
177     work->data = static_cast<void *>(dataWorker);
178 
179     int32_t ret = uv_queue_work(loop, work, [](uv_work_t *work) {}, UvQueueWorkOnExpired);
180     if (ret != 0) {
181         delete dataWorker;
182         dataWorker = nullptr;
183         delete work;
184         work = nullptr;
185         callbackInstances_.erase(findCallback);
186     }
187 }
188 
SetCallbackInfo(const napi_env & env,const napi_ref & ref)189 void CallbackInstance::SetCallbackInfo(const napi_env &env, const napi_ref &ref)
190 {
191     expiredCallbackInfo_.env = env;
192     expiredCallbackInfo_.ref = ref;
193 }
194 
GetExpiredCallback(const napi_env & env,const napi_value & value,std::shared_ptr<CallbackInstance> & callback)195 napi_value GetExpiredCallback(
196     const napi_env &env, const napi_value &value, std::shared_ptr<CallbackInstance> &callback)
197 {
198     napi_ref result = nullptr;
199     callback = std::make_shared<CallbackInstance>();
200     callback->Init();
201 
202     napi_create_reference(env, value, 1, &result);
203     callback->SetCallbackInfo(env, result);
204 
205     return Common::NapiGetNull(env);
206 }
207 
ParseParameters(const napi_env & env,const napi_callback_info & info,std::u16string & reason,std::shared_ptr<CallbackInstance> & callback,bool isThrow)208 napi_value ParseParameters(const napi_env &env, const napi_callback_info &info,
209     std::u16string &reason, std::shared_ptr<CallbackInstance> &callback, bool isThrow)
210 {
211     size_t argc = REQUEST_SUSPEND_DELAY_PARAMS;
212     napi_value argv[REQUEST_SUSPEND_DELAY_PARAMS] = {nullptr};
213     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
214     if (argc != REQUEST_SUSPEND_DELAY_PARAMS) {
215         Common::HandleParamErr(env, ERR_PARAM_NUMBER_ERR, isThrow);
216         return nullptr;
217     }
218 
219     // argv[0] : reason
220     if (Common::GetU16StringValue(env, argv[0], reason) == nullptr) {
221         BGTASK_LOGE("ParseParameters failed, reason is nullptr.");
222         Common::HandleParamErr(env, ERR_REASON_NULL_OR_TYPE_ERR, isThrow);
223         return nullptr;
224     }
225 
226     // arg[1] : callback
227     napi_valuetype valuetype = napi_undefined;
228     NAPI_CALL(env, napi_typeof(env, argv[1], &valuetype));
229     if (valuetype != napi_function) {
230         Common::HandleParamErr(env, ERR_CALLBACK_NULL_OR_TYPE_ERR, isThrow);
231         return nullptr;
232     }
233 
234     if (GetExpiredCallback(env, argv[1], callback) == nullptr) {
235         BGTASK_LOGE("ExpiredCallback parse failed");
236         Common::HandleParamErr(env, ERR_CALLBACK_NULL_OR_TYPE_ERR, isThrow);
237         return nullptr;
238     }
239     return Common::NapiGetNull(env);
240 }
241 
RequestSuspendDelay(napi_env env,napi_callback_info info,bool isThrow)242 napi_value RequestSuspendDelay(napi_env env, napi_callback_info info, bool isThrow)
243 {
244     HitraceScoped traceScoped(HITRACE_TAG_OHOS,
245         "BackgroundTaskManager::TransientTask::Napi::RequestSuspendDelay");
246 
247 #ifdef SUPPORT_JSSTACK
248     HiviewDFX::ReportXPowerJsStackSysEvent(env, "TRANSIENT_TASK_APPLY");
249 #endif
250     std::shared_ptr<CallbackInstance> callback = nullptr;
251     std::u16string reason;
252     if (ParseParameters(env, info, reason, callback, isThrow) == nullptr) {
253         return Common::NapiGetNull(env);
254     }
255 
256     std::shared_ptr<DelaySuspendInfo> delaySuspendInfo {nullptr};
257     ErrCode errCode = DelayedSingleton<BackgroundTaskManager>::GetInstance()->
258         RequestSuspendDelay(reason, *callback, delaySuspendInfo);
259     Common::HandleErrCode(env, errCode, isThrow);
260     if (!delaySuspendInfo) {
261         return Common::NapiGetNull(env);
262     }
263     {
264         std::lock_guard<std::mutex> lock(callbackLock_);
265         callbackInstances_[delaySuspendInfo->GetRequestId()] = callback;
266     }
267 
268     napi_value result = nullptr;
269     napi_create_object(env, &result);
270     if (!Common::SetDelaySuspendInfo(env, delaySuspendInfo, result)) {
271         BGTASK_LOGW("Set DelaySuspendInfo object failed");
272     }
273     return result;
274 }
275 
RequestSuspendDelay(napi_env env,napi_callback_info info)276 napi_value RequestSuspendDelay(napi_env env, napi_callback_info info)
277 {
278     return RequestSuspendDelay(env, info, false);
279 }
280 
RequestSuspendDelayThrow(napi_env env,napi_callback_info info)281 napi_value RequestSuspendDelayThrow(napi_env env, napi_callback_info info)
282 {
283     return RequestSuspendDelay(env, info, true);
284 }
285 }  // namespace BackgroundTaskMgr
286 }  // namespace OHOS