• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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