1/*
2 * Copyright (c) 2021-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 "native_async_work.h"
17
18#ifdef ENABLE_HITRACE
19#include "hitrace/trace.h"
20#include "hitrace_meter.h"
21#include "parameter.h"
22#include <securec.h>
23#endif
24#ifdef ENABLE_CONTAINER_SCOPE
25#include "core/common/container_scope.h"
26#endif
27
28#include <cinttypes>
29#include "ecmascript/napi/include/jsnapi.h"
30#include "native_api_internal.h"
31#include "napi/native_api.h"
32#include "native_engine.h"
33#include "utils/log.h"
34
35#ifdef ENABLE_CONTAINER_SCOPE
36using OHOS::Ace::ContainerScope;
37#endif
38
39#ifdef ENABLE_HITRACE
40bool g_napiTraceIdEnabled = false;
41bool g_ParamUpdated = false;
42constexpr size_t TRACE_BUFFER_SIZE = 120;
43constexpr size_t TRACEID_PARAM_SIZE = 10;
44const std::string TRACE_POINT_QUEUE = "napi::NativeAsyncWork::Queue";
45const std::string TRACE_POINT_QUEUE_WITH_QOS = "napi::NativeAsyncWork::QueueWithQos";
46const std::string TRACE_POINT_ASYNCWORKCALLBACK = "napi::NativeAsyncWork::AsyncWorkCallback";
47using namespace OHOS::HiviewDFX;
48#endif
49
50NativeAsyncWork::NativeAsyncWork(NativeEngine* engine,
51                                 NativeAsyncExecuteCallback execute,
52                                 NativeAsyncCompleteCallback complete,
53                                 const std::string &asyncResourceName,
54                                 void* data)
55    : work_({ 0 }), engine_(engine), engineId_(engine->GetId()), execute_(execute), complete_(complete), data_(data)
56{
57    work_.data = this;
58    (void)asyncResourceName;
59#ifdef ENABLE_HITRACE
60    if (!g_ParamUpdated) {
61        char napiTraceIdEnabled[TRACEID_PARAM_SIZE] = {0};
62        int ret = GetParameter("persist.hiviewdfx.napitraceid.enabled", "false",
63            napiTraceIdEnabled, sizeof(napiTraceIdEnabled));
64        if (ret > 0 && strcmp(napiTraceIdEnabled, "true") == 0) {
65            g_napiTraceIdEnabled = true;
66        }
67        g_ParamUpdated = true;
68    }
69    bool createdTraceId = false;
70
71    HiTraceId thisId = HiTraceChain::GetId();
72    if (g_napiTraceIdEnabled && (!thisId.IsValid())) {
73        thisId = HiTraceChain::Begin("New NativeAsyncWork", 0);
74        createdTraceId = true;
75    }
76    if (thisId.IsValid()) {
77        taskTraceId_ = HiTraceChain::CreateSpan();
78    }
79    char traceStr[TRACE_BUFFER_SIZE] = {0};
80    if (sprintf_s(traceStr, sizeof(traceStr),
81        "name:%s, traceid:0x%x", asyncResourceName.c_str(), taskTraceId_.GetChainId()) < 0) {
82        HILOG_ERROR("Get traceStr fail");
83    }
84    traceDescription_ = traceStr;
85    if (createdTraceId) {
86        OHOS::HiviewDFX::HiTraceChain::ClearId();
87    }
88#endif
89#ifdef ENABLE_CONTAINER_SCOPE
90    containerScopeId_ = ContainerScope::CurrentId();
91#endif
92}
93
94NativeAsyncWork::~NativeAsyncWork() = default;
95
96bool NativeAsyncWork::Queue()
97{
98    if (engineId_ != engine_->GetId()) {
99        LOG_IF_SPECIAL(UNLIKELY(engine_->IsCrossThreadCheckEnabled()),
100                       "owner env has been destroyed, "
101                       "current env id: %{public}" PRIu64 ", owner id: %{public}" PRIu64,
102                       engineId_, engine_->GetId());
103    }
104
105    uv_loop_t* loop = engine_->GetUVLoop();
106    if (loop == nullptr) {
107        HILOG_ERROR("Get loop failed");
108        return false;
109    }
110    engine_->IncreaseWaitingRequestCounter();
111#ifdef ENABLE_HITRACE
112    StartTrace(HITRACE_TAG_ACE, "Napi queue, " + this->GetTraceDescription());
113    HiTraceId taskId = taskTraceId_;
114    HiTraceChain::Tracepoint(HITRACE_TP_CS, taskId, "%s", TRACE_POINT_QUEUE.c_str());
115#endif
116    int status = uv_queue_work(loop, &work_, AsyncWorkCallback, AsyncAfterWorkCallback);
117#ifdef ENABLE_HITRACE
118    HiTraceChain::Tracepoint(HITRACE_TP_CR, taskId, "%s", TRACE_POINT_QUEUE.c_str());
119    FinishTrace(HITRACE_TAG_ACE);
120#endif
121    if (status != 0) {
122        HILOG_ERROR("uv_queue_work failed");
123        engine_->DecreaseWaitingRequestCounter();
124        return false;
125    }
126    HILOG_DEBUG("uv_queue_work succeed");
127    return true;
128}
129
130bool NativeAsyncWork::QueueWithQos(napi_qos_t qos)
131{
132    if (engineId_ != engine_->GetId()) {
133        LOG_IF_SPECIAL(UNLIKELY(engine_->IsCrossThreadCheckEnabled()),
134                       "param env is not equal to its owner, "
135                       "current env id: %{public}" PRIu64 ", owner id: %{public}" PRIu64,
136                       engineId_, engine_->GetId());
137    }
138
139    uv_loop_t* loop = engine_->GetUVLoop();
140    if (loop == nullptr) {
141        HILOG_ERROR("Get loop failed");
142        return false;
143    }
144    engine_->IncreaseWaitingRequestCounter();
145#ifdef ENABLE_HITRACE
146    StartTrace(HITRACE_TAG_ACE, "Napi queueWithQos, " + this->GetTraceDescription());
147    HiTraceId taskId = taskTraceId_;
148    HiTraceChain::Tracepoint(HITRACE_TP_CS, taskId, "%s", TRACE_POINT_QUEUE_WITH_QOS.c_str());
149#endif
150    int status = uv_queue_work_with_qos(loop, &work_, AsyncWorkCallback, AsyncAfterWorkCallback, uv_qos_t(qos));
151#ifdef ENABLE_HITRACE
152    HiTraceChain::Tracepoint(HITRACE_TP_CR, taskId, "%s", TRACE_POINT_QUEUE_WITH_QOS.c_str());
153    FinishTrace(HITRACE_TAG_ACE);
154#endif
155    if (status != 0) {
156        HILOG_ERROR("uv_queue_work_with_qos failed");
157        engine_->DecreaseWaitingRequestCounter();
158        return false;
159    }
160    HILOG_DEBUG("uv_queue_work_with_qos succeed");
161    return true;
162}
163
164bool NativeAsyncWork::Cancel()
165{
166    if (engineId_ != engine_->GetId()) {
167        LOG_IF_SPECIAL(UNLIKELY(engine_->IsCrossThreadCheckEnabled()),
168                       "param env is not equal to its owner, "
169                       "current env id: %{public}" PRIu64 ", owner id: %{public}" PRIu64,
170                       engineId_, engine_->GetId());
171    }
172
173    int status = uv_cancel((uv_req_t*)&work_);
174    if (status != 0) {
175        HILOG_ERROR("uv_cancel failed");
176        return false;
177    }
178    return true;
179}
180
181void NativeAsyncWork::AsyncWorkCallback(uv_work_t* req)
182{
183    if (req == nullptr) {
184        HILOG_ERROR("req is nullptr");
185        return;
186    }
187
188    auto that = reinterpret_cast<NativeAsyncWork*>(req->data);
189    HILOG_DEBUG("NativeAsyncWork::AsyncWorkCallback start to execute.");
190
191#ifdef ENABLE_HITRACE
192    StartTrace(HITRACE_TAG_ACE, "Napi execute, " + that->GetTraceDescription());
193    if (that->taskTraceId_.IsValid()) {
194        HiTraceId currentId = HiTraceChain::SaveAndSet(that->taskTraceId_);
195        HiTraceChain::Tracepoint(HITRACE_TP_SR, that->taskTraceId_, "%s", TRACE_POINT_ASYNCWORKCALLBACK.c_str());
196        that->execute_(that->engine_, that->data_);
197        FinishTrace(HITRACE_TAG_ACE);
198        HiTraceChain::Tracepoint(HITRACE_TP_SS, that->taskTraceId_, "%s", TRACE_POINT_ASYNCWORKCALLBACK.c_str());
199        HiTraceChain::Restore(currentId);
200        return;
201    }
202#endif
203    that->execute_(that->engine_, that->data_);
204#ifdef ENABLE_HITRACE
205    FinishTrace(HITRACE_TAG_ACE);
206#endif
207}
208
209void NativeAsyncWork::AsyncAfterWorkCallback(uv_work_t* req, int status)
210{
211    if (req == nullptr) {
212        HILOG_ERROR("req is nullptr");
213        return;
214    }
215
216    auto that = reinterpret_cast<NativeAsyncWork*>(req->data);
217    auto engine = that->engine_;
218    engine->DecreaseWaitingRequestCounter();
219    auto vm = engine->GetEcmaVm();
220    panda::LocalScope scope(vm);
221    napi_status nstatus = napi_generic_failure;
222    switch (status) {
223        case 0:
224            nstatus = napi_ok;
225            break;
226        case (int)UV_EINVAL:
227            nstatus = napi_invalid_arg;
228            break;
229        case (int)UV_ECANCELED:
230            nstatus = napi_cancelled;
231            break;
232        default:
233            nstatus = napi_generic_failure;
234    }
235#ifdef ENABLE_CONTAINER_SCOPE
236    ContainerScope containerScope(that->containerScopeId_);
237#endif
238
239    TryCatch tryCatch(reinterpret_cast<napi_env>(engine));
240    HILOG_DEBUG("NativeAsyncWork::AsyncAfterWorkCallback start to execute.");
241#ifdef ENABLE_HITRACE
242    StartTrace(HITRACE_TAG_ACE, "Napi complete, " + that->GetTraceDescription());
243    bool isValidTraceId = that->taskTraceId_.IsValid();
244    if (isValidTraceId) {
245        OHOS::HiviewDFX::HiTraceChain::SaveAndSet(that->taskTraceId_);
246    }
247#endif
248
249    // Don't use that after complete
250    that->complete_(engine, nstatus, that->data_);
251    if (tryCatch.HasCaught()) {
252        engine->HandleUncaughtException();
253    }
254
255#ifdef ENABLE_HITRACE
256    FinishTrace(HITRACE_TAG_ACE);
257    if (isValidTraceId) {
258        OHOS::HiviewDFX::HiTraceChain::ClearId();
259    }
260#endif
261}
262
263std::string NativeAsyncWork::GetTraceDescription()
264{
265    return traceDescription_;
266}
267