1 /*
2 * Copyright (C) 2024 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 "PromiseBase.h"
17
18 #include <meta/api/make_callback.h>
19 #include <meta/interface/intf_task_queue_registry.h>
20 #include <napi_api.h>
21
22 static constexpr BASE_NS::Uid JS_RELEASE_THREAD { "3784fa96-b25b-4e9c-bbf1-e897d36f73af" };
23
PromiseBase(napi_env env)24 PromiseBase::PromiseBase(napi_env env) : env_(env)
25 {
26 CreateJsPromise();
27 CreateSettleFunc();
28 }
29
~PromiseBase()30 PromiseBase::~PromiseBase() {}
31
ToNapiValue() const32 napi_value PromiseBase::ToNapiValue() const
33 {
34 return promise_;
35 }
36
SettleLater()37 void PromiseBase::SettleLater()
38 {
39 auto task = META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move([this]() {
40 napi_call_threadsafe_function(
41 settleLaterFunc_, nullptr, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
42 return false;
43 }));
44 // An extra task in JS_RELEASE_THREAD to trigger this is required to work around an issue
45 // where SettleLater is called *in* an eventhandler.
46 // There are rare cases where napi_release_threadsafe_function waits for threadsafe function completion
47 // and the "JS function" is waiting for the engine thread (which is blocked releasing the function)
48 META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_RELEASE_THREAD)->AddTask(task);
49 }
50
SettleNow()51 void PromiseBase::SettleNow()
52 {
53 if (SetResult()) {
54 Resolve();
55 } else {
56 Reject();
57 }
58 ReleaseSettleFunc();
59 }
60
Reject()61 void PromiseBase::Reject()
62 {
63 if (!result_) {
64 napi_get_undefined(env_, &result_);
65 }
66 napi_reject_deferred(env_, deferred_, result_);
67 ReleaseSettleFunc();
68 }
69
Resolve()70 void PromiseBase::Resolve()
71 {
72 if (!result_) {
73 napi_get_undefined(env_, &result_);
74 }
75 napi_resolve_deferred(env_, deferred_, result_);
76 }
77
CreateJsPromise()78 void PromiseBase::CreateJsPromise()
79 {
80 auto status = napi_create_promise(env_, &deferred_, &promise_);
81 if (status != napi_ok) {
82 CORE_LOG_F("Failed to create promise");
83 Abort();
84 }
85 }
86
CreateSettleFunc()87 void PromiseBase::CreateSettleFunc()
88 {
89 napi_value name;
90 napi_create_string_latin1(env_, "async_promise_settle_func", NAPI_AUTO_LENGTH, &name);
91 auto settleContext = this;
92 auto finalizerDataArg = this;
93 auto status = napi_create_threadsafe_function(
94 env_, nullptr, nullptr, name, 1, 1, finalizerDataArg, FinalizeCB, settleContext, SettleCB, &settleLaterFunc_);
95 if (status != napi_ok) {
96 CORE_LOG_F("Failed to create a threadsafe JS function");
97 Abort();
98 return;
99 }
100 napi_ref_threadsafe_function(env_, settleLaterFunc_);
101 }
102
ReleaseSettleFunc()103 void PromiseBase::ReleaseSettleFunc()
104 {
105 // The finalizer of the settle later function deletes this promise object.
106 napi_unref_threadsafe_function(env_, settleLaterFunc_);
107 napi_release_threadsafe_function(settleLaterFunc_, napi_threadsafe_function_release_mode::napi_tsfn_release);
108 }
109
Abort()110 void PromiseBase::Abort()
111 {
112 const napi_extended_error_info* errorInfo { nullptr };
113 napi_get_last_error_info(env_, &errorInfo);
114 auto originalMessage = errorInfo->error_message;
115 bool exceptionPending { false };
116 napi_is_exception_pending(env_, &exceptionPending);
117 if (exceptionPending) {
118 // Not allowed to call most napi functions, so can't clean up.
119 deferred_ = nullptr;
120 promise_ = nullptr;
121 } else {
122 napi_get_undefined(env_, &result_);
123 if (deferred_) {
124 napi_reject_deferred(env_, deferred_, result_);
125 }
126 auto message = (originalMessage != nullptr) ? originalMessage : "Error initializing Promise";
127 napi_throw_error(env_, nullptr, message);
128 }
129 settleLaterFunc_ = nullptr;
130 }
131
SettleCB(napi_env env,napi_value js_callback,void * context,void * inData)132 void PromiseBase::SettleCB(napi_env env, napi_value js_callback, void* context, void* inData)
133 {
134 // This is run in the JS thread, so be careful with the calls to engine.
135 PromiseBase* self = (PromiseBase*)context;
136 self->SettleNow();
137 }
138
FinalizeCB(napi_env env,void * finalize_data,void * finalize_hint)139 void PromiseBase::FinalizeCB(napi_env env, void* finalize_data, void* finalize_hint)
140 {
141 PromiseBase* self = (PromiseBase*)finalize_data;
142 delete self;
143 }
144