• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifndef PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H
16 #define PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H
17 
18 #include "libpandabase/mem/object_pointer.h"
19 #include "plugins/ets/runtime/ets_coroutine.h"
20 #include "plugins/ets/runtime/types/ets_object.h"
21 #include "plugins/ets/runtime/types/ets_waiters_list.h"
22 
23 #include <cstdint>
24 
25 namespace ark::ets {
26 
27 template <typename T>
28 class EtsHandle;
29 
30 namespace test {
31 class EtsSyncPrimitivesTest;
32 }  // namespace test
33 
34 /// @brief The base class for sync primitives. Should not be used directly.
35 template <typename T>
36 class EtsSyncPrimitive : public EtsObject {
37 public:
FromCoreType(ObjectHeader * syncPrimitive)38     static T *FromCoreType(ObjectHeader *syncPrimitive)
39     {
40         return reinterpret_cast<T *>(syncPrimitive);
41     }
42 
FromEtsObject(EtsObject * syncPrimitive)43     static T *FromEtsObject(EtsObject *syncPrimitive)
44     {
45         return reinterpret_cast<T *>(syncPrimitive);
46     }
47 
AsObject()48     EtsObject *AsObject()
49     {
50         return this;
51     }
52 
AsObject()53     const EtsObject *AsObject() const
54     {
55         return this;
56     }
57 
GetWaitersList(EtsCoroutine * coro)58     EtsWaitersList *GetWaitersList(EtsCoroutine *coro)
59     {
60         return EtsWaitersList::FromCoreType(
61             ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsSyncPrimitive, waitersList_)));
62     }
63 
SetWaitersList(EtsCoroutine * coro,EtsWaitersList * waitersList)64     void SetWaitersList(EtsCoroutine *coro, EtsWaitersList *waitersList)
65     {
66         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsSyncPrimitive, waitersList_),
67                                   waitersList->GetCoreType());
68     }
69 
70     /**
71      * Blocks current coroutine. It can be used concurrently with other Suspend and Resume
72      * @param awaitee - EtsWaitersList node that contains GenericEvent and probably other useful data
73      * NOTE: `this` and all other raw ObjectHeaders may become invalid after this call due to GC
74      */
SuspendCoroutine(EtsWaitersList::Node * awaitee)75     ALWAYS_INLINE void SuspendCoroutine(EtsWaitersList::Node *awaitee)
76     {
77         auto *coro = EtsCoroutine::GetCurrent();
78         auto *coroManager = coro->GetCoroutineManager();
79         auto &event = awaitee->GetEvent();
80         // Need to lock event before PushBack
81         // to avoid use-after-free in CoroutineEvent::Happen method
82         event.Lock();
83         GetWaitersList(coro)->PushBack(awaitee);
84         coroManager->Await(&event);
85     }
86 
87     /**
88      * Unblocks suspended coroutine. It can be used concurrently with Suspend,
89      * BUT not with other Resume (PopFront is not thread-safety)
90      */
ResumeCoroutine()91     ALWAYS_INLINE void ResumeCoroutine()
92     {
93         auto *coro = EtsCoroutine::GetCurrent();
94         auto *awaitee = GetWaitersList(coro)->PopFront();
95         awaitee->GetEvent().Happen();
96     }
97 
98 private:
99     ObjectPointer<EtsWaitersList> waitersList_;
100 
101     friend class test::EtsSyncPrimitivesTest;
102 };
103 
104 /// @brief Coroutine mutex. This allows to get exclusive access to the critical section.
105 class EtsMutex : public EtsSyncPrimitive<EtsMutex> {
106 public:
107     template <typename T>
108     class LockHolder {
109     public:
LockHolder(EtsHandle<T> & hLock)110         explicit LockHolder(EtsHandle<T> &hLock) : hLock_(hLock)
111         {
112             hLock_->Lock();
113         }
114 
115         NO_COPY_SEMANTIC(LockHolder);
116         NO_MOVE_SEMANTIC(LockHolder);
117 
~LockHolder()118         ~LockHolder()
119         {
120             hLock_->Unlock();
121         }
122 
123     private:
124         EtsHandle<T> &hLock_;
125     };
126 
127     /**
128      * Method gives exclusive access to critical section is case of successful mutex lock.
129      * Otherwise, blocks current coroutine until other coroutine will unlock the mutex.
130      */
131     void Lock();
132 
133     /**
134      * Calling the method means exiting the critical section.
135      * If there are blocked coroutines, unblocks one of them.
136      */
137     void Unlock();
138 
139     /// This method should be used to make sure that mutex is locked by current coroutine.
140     bool IsHeld();
141 
142     static EtsMutex *Create(EtsCoroutine *coro);
143 
144 private:
145     std::atomic<uint32_t> waiters_;
146 
147     friend class test::EtsSyncPrimitivesTest;
148 };
149 
150 /// @brief Coroutine one-shot event. This allows to block current coroutine until event is fired
151 class EtsEvent : public EtsSyncPrimitive<EtsEvent> {
152 public:
153     /**
154      * Blocks current coroutine until another coroutine will fire the same event.
155      * It can be used concurrently with other wait/fire.
156      * It has no effect in case fire has already been caused, but guarantees happens-before.
157      */
158     void Wait();
159 
160     /**
161      * Unblocks all coroutines that are waiting the same event.
162      * Can be used concurrently with other wait/fire but multiply calls have no effect.
163      */
164     void Fire();
165 
166     static EtsEvent *Create(EtsCoroutine *coro);
167 
168 private:
169     static constexpr uint32_t STATE_BIT = 1U;
170     static constexpr uint32_t FIRE_STATE = 1U;
171     static constexpr uint32_t ONE_WAITER = 2U;
172 
IsFireState(uint32_t state)173     static bool IsFireState(uint32_t state)
174     {
175         return (state & STATE_BIT) == FIRE_STATE;
176     }
177 
GetNumberOfWaiters(uint32_t state)178     static uint32_t GetNumberOfWaiters(uint32_t state)
179     {
180         return state >> STATE_BIT;
181     }
182 
183     std::atomic<uint32_t> state_;
184 
185     friend class test::EtsSyncPrimitivesTest;
186 };
187 
188 /// Coroutine conditional variable
189 class EtsCondVar : public EtsSyncPrimitive<EtsCondVar> {
190 public:
191     /**
192      * precondition: mutex is locked
193      * 1. Unlocks mutex
194      * 2. Blocks current coroutine
195      * 3. Locks mutex
196      * This method is thread-safe in relation to other methods of the CondVar
197      */
198     void Wait(EtsHandle<EtsMutex> &mutex);
199 
200     /**
201      * precondition: mutex is locked
202      * Unblocks ONE suspended coroutine associated with this CondVar, if it exists.
203      * This method is thread-safe in relation to other methods of the CondVar
204      */
205     void NotifyOne([[maybe_unused]] EtsMutex *mutex);
206 
207     /**
208      * precondition: mutex is locked
209      * Unblocks ALL suspended coroutine associated with this CondVar.
210      * This method is thread-safe in relation to other methods of the CondVar
211      */
212     void NotifyAll([[maybe_unused]] EtsMutex *mutex);
213 
214     static EtsCondVar *Create(EtsCoroutine *coro);
215 
216 private:
217     EtsInt waiters_;
218 
219     friend class test::EtsSyncPrimitivesTest;
220 };
221 
222 }  // namespace ark::ets
223 
224 #endif  // PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H
225