* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "native_async_work.h"
#ifdef ENABLE_HITRACE
#include "hitrace_meter.h"
#include "parameter.h"
#endif
#ifdef ENABLE_CONTAINER_SCOPE
#include "native_container_scope.h"
#endif
#include <cinttypes>
#include "native_api_internal.h"
#ifdef ENABLE_HITRACE
std::atomic<bool> g_napiTraceIdEnabled(false);
std::atomic<bool> g_ParamUpdated(false);
constexpr size_t TRACE_BUFFER_SIZE = 120;
constexpr size_t TRACEID_PARAM_SIZE = 10;
const std::string TRACE_POINT_QUEUE = "napi::NativeAsyncWork::Queue";
const std::string TRACE_POINT_QUEUE_WITH_QOS = "napi::NativeAsyncWork::QueueWithQos";
const std::string TRACE_POINT_QUEUE_ORDERED = "napi::NativeAsyncWork::QueueOrdered";
const std::string TRACE_POINT_ASYNCWORKCALLBACK = "napi::NativeAsyncWork::AsyncWorkCallback";
using namespace OHOS::HiviewDFX;
#endif
NativeAsyncWork::NativeAsyncWork(NativeEngine* engine,
NativeAsyncExecuteCallback execute,
NativeAsyncCompleteCallback complete,
const std::string &asyncResourceName,
void* data)
: work_({ 0 }), engine_(engine), engineId_(engine->GetId()), execute_(execute), complete_(complete), data_(data)
{
work_.data = this;
taskName_ = asyncResourceName;
#ifdef ENABLE_HITRACE
if (!g_ParamUpdated.load()) {
char napiTraceIdEnabled[TRACEID_PARAM_SIZE] = {0};
int ret = GetParameter("persist.hiviewdfx.napitraceid.enabled", "false",
napiTraceIdEnabled, sizeof(napiTraceIdEnabled));
if (ret > 0 && strcmp(napiTraceIdEnabled, "true") == 0) {
g_napiTraceIdEnabled.store(true);
}
g_ParamUpdated.store(true);
}
bool createdTraceId = false;
HiTraceId thisId = HiTraceChain::GetId();
if (g_napiTraceIdEnabled.load() && (!thisId.IsValid())) {
thisId = HiTraceChain::Begin("New NativeAsyncWork", 0);
createdTraceId = true;
}
if (thisId.IsValid()) {
taskTraceId_ = HiTraceChain::CreateSpan();
}
char traceStr[TRACE_BUFFER_SIZE] = {0};
if (sprintf_s(traceStr, sizeof(traceStr),
"name:%s#%" PRIuPTR ", traceid:0x%x",
asyncResourceName.c_str(),
reinterpret_cast<uintptr_t>(this),
taskTraceId_.GetChainId()) < 0) {
HILOG_ERROR("Get traceStr fail");
}
traceDescription_ = traceStr;
if (createdTraceId) {
OHOS::HiviewDFX::HiTraceChain::ClearId();
}
#endif
#ifdef ENABLE_CONTAINER_SCOPE
if (engine->IsContainerScopeEnabled()) {
containerScopeId_ = engine->GetContainerScopeIdFunc();
}
#endif
}
NativeAsyncWork::~NativeAsyncWork() = default;
bool NativeAsyncWork::Queue(NativeEngine* engine)
{
VALID_ENGINE_CHECK(engine, engine_, engineId_);
uv_loop_t* loop = nullptr;
if (engine_->IsMainEnvContext()) {
loop = engine_->GetUVLoop();
} else {
loop = engine_->GetParent()->GetUVLoop();
}
if (loop == nullptr) {
HILOG_ERROR("Get loop failed");
return false;
}
engine_->IncreaseWaitingRequestCounter();
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "Native async work queue, " + this->GetTraceDescription());
HiTraceId taskId = taskTraceId_;
HiTraceChain::Tracepoint(HITRACE_TP_CS, taskId, "%s", TRACE_POINT_QUEUE.c_str());
#endif
int status = uv_queue_work_internal(loop, &work_, AsyncWorkCallback, AsyncAfterWorkCallback, taskName_.c_str());
#ifdef ENABLE_HITRACE
HiTraceChain::Tracepoint(HITRACE_TP_CR, taskId, "%s", TRACE_POINT_QUEUE.c_str());
FinishTrace(HITRACE_TAG_ACE);
#endif
if (status != 0) {
HILOG_ERROR("uv_queue_work failed");
engine_->DecreaseWaitingRequestCounter();
return false;
}
HILOG_DEBUG("uv_queue_work succeed");
return true;
}
bool NativeAsyncWork::QueueWithQos(NativeEngine* engine, napi_qos_t qos)
{
VALID_ENGINE_CHECK(engine, engine_, engineId_);
uv_loop_t* loop = nullptr;
if (engine_->IsMainEnvContext()) {
loop = engine_->GetUVLoop();
} else {
loop = engine_->GetParent()->GetUVLoop();
}
if (loop == nullptr) {
HILOG_ERROR("Get loop failed");
return false;
}
engine_->IncreaseWaitingRequestCounter();
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "Native async work queueWithQos, " + this->GetTraceDescription());
HiTraceId taskId = taskTraceId_;
HiTraceChain::Tracepoint(HITRACE_TP_CS, taskId, "%s", TRACE_POINT_QUEUE_WITH_QOS.c_str());
#endif
int status = uv_queue_work_with_qos_internal(loop, &work_, AsyncWorkCallback,
AsyncAfterWorkCallback, uv_qos_t(qos), taskName_.c_str());
#ifdef ENABLE_HITRACE
HiTraceChain::Tracepoint(HITRACE_TP_CR, taskId, "%s", TRACE_POINT_QUEUE_WITH_QOS.c_str());
FinishTrace(HITRACE_TAG_ACE);
#endif
if (status != 0) {
HILOG_ERROR("uv_queue_work_with_qos failed");
engine_->DecreaseWaitingRequestCounter();
return false;
}
HILOG_DEBUG("uv_queue_work_with_qos succeed");
return true;
}
bool NativeAsyncWork::QueueOrdered(NativeEngine* engine, napi_qos_t qos, uintptr_t queueId)
{
VALID_ENGINE_CHECK(engine, engine_, engineId_);
uv_loop_t* loop = nullptr;
if (engine_->IsMainEnvContext()) {
loop = engine_->GetUVLoop();
} else {
loop = engine_->GetParent()->GetUVLoop();
}
if (loop == nullptr) {
HILOG_ERROR("Get loop failed");
return false;
}
engine_->IncreaseWaitingRequestCounter();
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "Native async work queueOrdered, " + this->GetTraceDescription());
HiTraceId taskId = taskTraceId_;
HiTraceChain::Tracepoint(HITRACE_TP_CS, taskId, "%s", TRACE_POINT_QUEUE_ORDERED.c_str());
#endif
int status = uv_queue_work_ordered(loop, &work_, AsyncWorkCallback, AsyncAfterWorkCallback,
uv_qos_t(qos), queueId);
#ifdef ENABLE_HITRACE
HiTraceChain::Tracepoint(HITRACE_TP_CR, taskId, "%s", TRACE_POINT_QUEUE_ORDERED.c_str());
FinishTrace(HITRACE_TAG_ACE);
#endif
if (status != 0) {
HILOG_ERROR("uv_queue_work_ordered failed");
engine_->DecreaseWaitingRequestCounter();
return false;
}
HILOG_DEBUG("uv_queue_work_ordered succeed");
return true;
}
bool NativeAsyncWork::Cancel(NativeEngine* engine)
{
VALID_ENGINE_CHECK(engine, engine_, engineId_);
int status = uv_cancel((uv_req_t*)&work_);
if (status != 0) {
HILOG_ERROR("uv_cancel failed");
return false;
}
return true;
}
void NativeAsyncWork::AsyncWorkCallback(uv_work_t* req)
{
if (req == nullptr) {
HILOG_ERROR("req is nullptr");
return;
}
auto that = reinterpret_cast<NativeAsyncWork*>(req->data);
HILOG_DEBUG("NativeAsyncWork::AsyncWorkCallback start to execute.");
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "Native async work execute callback, " + that->GetTraceDescription());
if (that->taskTraceId_.IsValid()) {
HiTraceId currentId = HiTraceChain::SaveAndSet(that->taskTraceId_);
HiTraceChain::Tracepoint(HITRACE_TP_SR, that->taskTraceId_, "%s", TRACE_POINT_ASYNCWORKCALLBACK.c_str());
that->execute_(that->engine_, that->data_);
FinishTrace(HITRACE_TAG_ACE);
HiTraceChain::Tracepoint(HITRACE_TP_SS, that->taskTraceId_, "%s", TRACE_POINT_ASYNCWORKCALLBACK.c_str());
HiTraceChain::Restore(currentId);
return;
}
#endif
that->execute_(that->engine_, that->data_);
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
}
void NativeAsyncWork::AsyncAfterWorkCallback(uv_work_t* req, int status)
{
if (req == nullptr) {
HILOG_ERROR("req is nullptr");
return;
}
auto that = reinterpret_cast<NativeAsyncWork*>(req->data);
auto engine = that->engine_;
engine->DecreaseWaitingRequestCounter();
auto vm = engine->GetEcmaVm();
panda::LocalScope scope(vm);
napi_status nstatus = napi_generic_failure;
switch (status) {
case 0:
nstatus = napi_ok;
break;
case (int)UV_EINVAL:
nstatus = napi_invalid_arg;
break;
case (int)UV_ECANCELED:
nstatus = napi_cancelled;
break;
default:
nstatus = napi_generic_failure;
}
#ifdef ENABLE_CONTAINER_SCOPE
NapiContainerScope containerScope(engine, that->containerScopeId_, engine->IsContainerScopeEnabled());
#endif
TryCatch tryCatch(reinterpret_cast<napi_env>(engine));
HILOG_DEBUG("NativeAsyncWork::AsyncAfterWorkCallback start to execute.");
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE, "Native async work complete callback, " + that->GetTraceDescription());
bool isValidTraceId = that->taskTraceId_.IsValid();
if (isValidTraceId) {
OHOS::HiviewDFX::HiTraceChain::SaveAndSet(that->taskTraceId_);
}
#endif
auto complete = that->complete_;
auto description = that->GetTraceDescription();
NativeEngine::ExecuteCallback(__FUNCTION__, that->complete_, engine, nstatus, that->data_);
if (engine->HasCriticalScope()) {
HILOG_FATAL("critical scope still open after user callback (ID: %{public}" PRIuPTR
") returned, task description: %{public}s",
reinterpret_cast<uintptr_t>(complete), description.c_str());
}
if (tryCatch.HasCaught()) {
engine->HandleUncaughtException();
}
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
if (isValidTraceId) {
OHOS::HiviewDFX::HiTraceChain::ClearId();
}
#endif
}
std::string NativeAsyncWork::GetTraceDescription()
{
return traceDescription_;
}