1 /**
2 * Copyright (c) 2021-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 "intrinsics.h"
17 #include "plugins/ets/runtime/ets_utils.h"
18 #include "plugins/ets/runtime/ets_coroutine.h"
19 #include "plugins/ets/runtime/ets_vm.h"
20 #include "plugins/ets/runtime/types/ets_method.h"
21 #include "runtime/coroutines/coroutine_manager.h"
22 #include "plugins/ets/runtime/types/ets_promise.h"
23 #include "plugins/ets/runtime/ets_handle_scope.h"
24 #include "plugins/ets/runtime/ets_handle.h"
25 #include "plugins/ets/runtime/job_queue.h"
26 #include "plugins/ets/runtime/ets_callback.h"
27 #include "runtime/coroutines/stackful_coroutine.h"
28 #include "runtime/coroutines/coroutine_events.h"
29 #include "runtime/include/mem/panda_containers.h"
30
31 namespace ark::ets::intrinsics {
32
SubscribePromiseOnResultObject(EtsPromise * outsidePromise,EtsPromise * internalPromise)33 void SubscribePromiseOnResultObject(EtsPromise *outsidePromise, EtsPromise *internalPromise)
34 {
35 PandaVector<Value> args {Value(outsidePromise), Value(internalPromise)};
36
37 auto subscribeOnAnotherPromise = [&args]() {
38 EtsCoroutine::GetCurrent()->GetPandaVM()->GetClassLinker()->GetSubscribeOnAnotherPromiseMethod()->Invoke(
39 EtsCoroutine::GetCurrent(), args.data());
40 };
41
42 auto *mainT = EtsCoroutine::GetCurrent()->GetPandaVM()->GetCoroutineManager()->GetMainThread();
43 Coroutine *mainCoro = Coroutine::CastFromThread(mainT);
44 Coroutine *current = Coroutine::GetCurrent();
45 if (current != mainCoro && mainCoro->GetId() == current->GetId()) {
46 // Call ExecuteOnThisContext is possible only in the same thread.
47 mainCoro->GetContext<StackfulCoroutineContext>()->ExecuteOnThisContext(
48 &subscribeOnAnotherPromise, EtsCoroutine::GetCurrent()->GetContext<StackfulCoroutineContext>());
49 } else {
50 subscribeOnAnotherPromise();
51 }
52 }
53
EtsPromiseResolve(EtsPromise * promise,EtsObject * value)54 void EtsPromiseResolve(EtsPromise *promise, EtsObject *value)
55 {
56 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
57 if (promise == nullptr) {
58 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
59 ThrowNullPointerException(ctx, coro);
60 return;
61 }
62 // NOTE: actually we need to lock(*promise) in case of concurrent resolve/reject
63 if (promise->GetState() != EtsPromise::STATE_PENDING) {
64 return;
65 }
66 [[maybe_unused]] EtsHandleScope scope(coro);
67 EtsHandle<EtsPromise> hpromise(coro, promise);
68
69 if (value != nullptr && value->IsInstanceOf(coro->GetPandaVM()->GetClassLinker()->GetPromiseClass())) {
70 auto internalPromise = EtsPromise::FromEtsObject(value);
71 EtsHandle<EtsPromise> hInternalPromise(coro, internalPromise);
72 if (hInternalPromise->IsPending() || coro->GetCoroutineManager()->IsJsMode()) {
73 SubscribePromiseOnResultObject(hpromise.GetPtr(), hInternalPromise.GetPtr());
74 return;
75 }
76 if (hInternalPromise->IsRejected()) {
77 EtsPromiseReject(hpromise.GetPtr(), hInternalPromise->GetValue(coro));
78 return;
79 }
80 // We can use internal promise's value as return value
81 value = hInternalPromise->GetValue(coro);
82 }
83 hpromise->Resolve(coro, value);
84 }
85
EtsPromiseReject(EtsPromise * promise,EtsObject * error)86 void EtsPromiseReject(EtsPromise *promise, EtsObject *error)
87 {
88 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
89 if (promise == nullptr) {
90 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
91 ThrowNullPointerException(ctx, coro);
92 return;
93 }
94 if (promise->GetState() != EtsPromise::STATE_PENDING) {
95 return;
96 }
97 promise->Reject(coro, error);
98 }
99
EtsPromiseSubmitCallback(EtsPromise * promise,EtsObject * callback)100 void EtsPromiseSubmitCallback(EtsPromise *promise, EtsObject *callback)
101 {
102 auto *coro = EtsCoroutine::GetCurrent();
103 promise->Lock();
104 if (promise->GetState() != EtsPromise::STATE_PENDING) {
105 ASSERT(promise->GetQueueSize() == 0);
106 ASSERT(promise->GetCallbackQueue(coro) == nullptr);
107 ASSERT(promise->GetCoroPtrQueue(coro) == nullptr);
108 promise->Unlock();
109 auto thenCallback = EtsCallback::Create(coro, callback);
110 coro->AddCallback(std::move(thenCallback));
111 return;
112 }
113 [[maybe_unused]] EtsHandleScope scope(coro);
114 EtsHandle<EtsPromise> hpromise(coro, promise);
115 EtsHandle<EtsObject> hcallback(coro, callback);
116 // NOTE(panferovi): fix issue with GC
117 // The problem is we can create new EtsArray inside method
118 // Promise::SubmitCallback and it may trigger GC.
119 // In case someone else tries to lock the promise, it will lead to a deadlock
120 hpromise->SubmitCallback(coro, hcallback);
121 coro->AnnounceCallbackAddition();
122 hpromise->Unlock();
123 }
124
EtsPromiseCreateLink(EtsObject * source,EtsPromise * target)125 void EtsPromiseCreateLink(EtsObject *source, EtsPromise *target)
126 {
127 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
128 auto *jobQueue = currentCoro->GetPandaVM()->GetJobQueue();
129 if (jobQueue != nullptr) {
130 jobQueue->CreateLink(source, target->AsObject());
131 }
132 }
133
AwaitProxyPromise(EtsCoroutine * currentCoro,EtsHandle<EtsPromise> & promiseHandle)134 static EtsObject *AwaitProxyPromise(EtsCoroutine *currentCoro, EtsHandle<EtsPromise> &promiseHandle)
135 {
136 /**
137 * This is a backed by JS equivalent promise.
138 * STS mode: error, no one can create such a promise!
139 * JS mode:
140 * - add a callback to JQ, that will:
141 * - resolve the promise with some value OR reject it
142 * - unblock the coro via event
143 * - schedule();
144 * - create a blocker event and link it to the promise
145 * - block current coroutine on the event
146 * - Schedule();
147 * (the last two steps are actually the cm->await()'s job)
148 * - return promise.value() if resolved or throw() it if rejected
149 */
150 EtsPromiseCreateLink(promiseHandle->GetLinkedPromise(currentCoro), promiseHandle.GetPtr());
151
152 auto *coroManager = currentCoro->GetCoroutineManager();
153 PandaUniquePtr<CoroutineEvent> e = MakePandaUnique<GenericEvent>(coroManager);
154 e->Lock();
155 promiseHandle->SetEventPtr(e.get());
156 coroManager->Await(e.get());
157 // will get here after the JS callback is called
158 if (promiseHandle->IsResolved()) {
159 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been resolved.";
160 return promiseHandle->GetValue(currentCoro);
161 }
162 // rejected
163 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been rejected.";
164 auto *exc = promiseHandle->GetValue(currentCoro);
165 currentCoro->SetException(exc->GetCoreType());
166 return nullptr;
167 }
168
EtsAwaitPromise(EtsPromise * promise)169 EtsObject *EtsAwaitPromise(EtsPromise *promise)
170 {
171 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
172 if (promise == nullptr) {
173 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
174 ThrowNullPointerException(ctx, currentCoro);
175 return nullptr;
176 }
177 [[maybe_unused]] EtsHandleScope scope(currentCoro);
178 EtsHandle<EtsPromise> promiseHandle(currentCoro, promise);
179
180 /* CASE 1. This is a converted JS promise */
181 if (promiseHandle->IsProxy()) {
182 return AwaitProxyPromise(currentCoro, promiseHandle);
183 }
184
185 /* CASE 2. This is a native STS promise */
186 while (true) {
187 currentCoro->ProcessPresentCallbacks();
188
189 promiseHandle->Lock();
190 if (!promiseHandle->IsPending()) {
191 // already settled!
192 promiseHandle->Unlock();
193
194 /**
195 * The promise is already resolved of rejected. Further actions:
196 * STS mode:
197 * if resolved: return Promise.value
198 * if rejected: throw Promise.value
199 * JS mode: NOTE!
200 * - suspend coro, create resolved JS promise and put it to the Q, on callback resume the coro
201 * and possibly throw
202 * - JQ::put(current_coro, promise)
203 *
204 */
205 if (promiseHandle->IsResolved()) {
206 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already resolved!";
207 return promiseHandle->GetValue(currentCoro);
208 }
209 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already rejected!";
210 auto *exc = promiseHandle->GetValue(currentCoro);
211 currentCoro->SetException(exc->GetCoreType());
212 return nullptr;
213 }
214
215 // the promise is not resolved yet
216 CoroutineEvent *e = promiseHandle->GetOrCreateEventPtr();
217 // it is necessary to check the type of event here under lock,
218 // because otherwise resolvee coro may destroy resources
219 auto isGeneric = e->GetType() == CoroutineEvent::Type::GENERIC;
220 // NOTE(konstanting, #I67QXC): try to make the Promise/Event locking sequence easier for understanding
221 auto noReadyCallbacks = currentCoro->TryEnterAwaitModeAndLockAwaitee(e);
222 promiseHandle->Unlock();
223 if (noReadyCallbacks) {
224 LOG(DEBUG, COROUTINES) << "Promise::await: starting await() for a pending promise...";
225 ASSERT(!e->Happened());
226 currentCoro->GetCoroutineManager()->Await(e); // will unlock the event
227 currentCoro->LeaveAwaitMode();
228 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished.";
229 }
230 if (isGeneric) {
231 promiseHandle->RetireEventPtr(e);
232 }
233 }
234 }
235 } // namespace ark::ets::intrinsics
236