1 /**
2 * Copyright (c) 2021-2025 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 "runtime/monitor.h"
17
18 #include "libpandabase/os/thread.h"
19 #include "runtime/include/object_header.h"
20 #include "runtime/include/runtime.h"
21 #include "runtime/include/runtime_notification.h"
22 #include "runtime/include/thread_scopes.h"
23 #include "runtime/include/thread-inl.h"
24 #include "runtime/interpreter/runtime_interface.h"
25 #include "runtime/mark_word.h"
26 #include "runtime/monitor_pool.h"
27 #include "runtime/handle_base-inl.h"
28 #include "runtime/mem/vm_handle.h"
29
30 #include <cinttypes>
31 #include <string>
32 #include <sched.h>
33
34 namespace ark {
35 template <typename T>
36 template <typename Predicate>
RemoveIf(Predicate pred)37 bool ThreadList<T>::RemoveIf(Predicate pred)
38 {
39 bool found = false;
40 auto prev = head_;
41 for (auto current = head_; current != nullptr; current = current->GetNextWait()) {
42 if (pred(*current)) {
43 found = true;
44 EraseAfter(prev, current);
45 current = prev;
46 } else {
47 prev = current;
48 }
49 }
50 return found;
51 }
52
53 template <typename T>
Splice(ThreadList & other)54 void ThreadList<T>::Splice(ThreadList &other)
55 {
56 if (Empty()) {
57 head_ = other.head_;
58 } else {
59 T *last = head_;
60 for (; last->GetNextWait() != nullptr; last = last->GetNextWait()) {
61 }
62 last->SetWaitNext(other.head_);
63 }
64 other.Clear();
65 }
66
67 template <typename T>
EraseAfter(T * prev,T * current)68 void ThreadList<T>::EraseAfter(T *prev, T *current)
69 {
70 if (current == head_) {
71 head_ = current->GetNextWait();
72 } else {
73 prev->SetWaitNext(current->GetNextWait());
74 }
75 }
76
77 template <typename T>
PopFront()78 void ThreadList<T>::PopFront()
79 {
80 head_ = head_->GetNextWait();
81 }
82
83 template <typename T>
PushFront(T & thread)84 void ThreadList<T>::PushFront(T &thread)
85 {
86 thread.SetWaitNext(head_);
87 head_ = &thread;
88 }
89
InflateThinLock(MTManagedThread * thread,const VMHandle<ObjectHeader> & objHandle)90 void Monitor::InflateThinLock(MTManagedThread *thread, [[maybe_unused]] const VMHandle<ObjectHeader> &objHandle)
91 {
92 #if defined(PANDA_USE_FUTEX)
93 // Futex inflation policy: suspend target thread, wait until it actually gets suspended
94 // and try inflating light monitor (`Inflate` expects lock to still be acquired by target;
95 // otherwise markword CAS fails). If it fails (i.e. thread got suspended when this monitor is
96 // no longer taken), we restart lightlock acquisition policy again.
97 // Compared to forced inflation (actively retry inflation once MAX_TRYLOCK_RETRY is reached
98 // or inflate monitor once this thread acquires light lock), this policy yields much better
99 // performance for short running synchronized blocks or functions, and is still expected to
100 // succeeed on longer blocks which should have safepoints and suspend successfully with
101 // monitor still acquired.
102 // We are trying to inflate light lock acquired by other thread, suspend it first
103 MTManagedThread *owner = nullptr;
104 ASSERT(objHandle.GetPtr() != nullptr);
105 MarkWord mark = objHandle.GetPtr()->AtomicGetMark();
106 auto threadManager = reinterpret_cast<MTThreadManager *>(thread->GetVM()->GetThreadManager());
107 os::thread::ThreadId ownerThreadId = mark.GetThreadId();
108 {
109 ScopedChangeThreadStatus ets(thread, ThreadStatus::IS_WAITING_INFLATION);
110 owner = threadManager->SuspendAndWaitThreadByInternalThreadId(ownerThreadId);
111 }
112 thread->SetEnterMonitorObject(nullptr);
113 thread->SetWaitingMonitorOldStatus(ThreadStatus::FINISHED);
114 // Thread could have finished by the time we tried stopping it
115 if (owner != nullptr) {
116 // NB! Inflate can do nothing if monitor is already unlocked or acquired by other thread.
117 Inflate<true>(objHandle.GetPtr(), owner);
118 {
119 // ResumeImpl can be called during thread termination, which leads to destruction of locked suspend_lock_
120 // inside ResumeImpl.
121 os::memory::LockHolder threadLock(*threadManager->GetThreadsLock());
122 owner->ResumeImpl(true);
123 }
124 }
125 #else
126 // Non-futex inflation policy: Wait until light lock is released, acquire it and inflate
127 // to heavy monitor
128 {
129 static constexpr uint64_t SLEEP_MS = 10;
130 thread->TimedWait(ThreadStatus::IS_WAITING_INFLATION, SLEEP_MS, 0);
131 }
132 thread->SetEnterMonitorObject(nullptr);
133 thread->SetWaitingMonitorOldStatus(ThreadStatus::FINISHED);
134 #endif
135 }
136
HandleLightLockedState(MarkWord & mark,MTManagedThread * thread,VMHandle<ObjectHeader> & objHandle,uint32_t & lightlockRetryCount,bool & shouldInflate,bool trylock)137 std::optional<Monitor::State> Monitor::HandleLightLockedState(MarkWord &mark, MTManagedThread *thread,
138 VMHandle<ObjectHeader> &objHandle,
139 uint32_t &lightlockRetryCount,
140 [[maybe_unused]] bool &shouldInflate, bool trylock)
141 {
142 os::thread::ThreadId ownerThreadId = mark.GetThreadId();
143 if (ownerThreadId == thread->GetInternalId()) {
144 uint32_t newCount = mark.GetLockCount() + 1;
145 if (newCount < MarkWord::LIGHT_LOCK_LOCK_MAX_COUNT) {
146 auto newMark = mark.DecodeFromLightLock(thread->GetInternalId(), newCount);
147 // Strong CAS as the loop iteration is large
148 ASSERT(objHandle.GetPtr() != nullptr);
149 bool ret = objHandle.GetPtr()->AtomicSetMark(mark, newMark);
150 if (ret) {
151 LOG(DEBUG, RUNTIME) << "The lightweight monitor was successfully recursively acquired";
152 Monitor::TraceMonitorLock(objHandle.GetPtr(), false);
153 thread->PushLocalObjectLocked(objHandle.GetPtr());
154 return Monitor::State::OK;
155 }
156 } else {
157 Monitor::Inflate(objHandle.GetPtr(), thread);
158 // Inflate set up recursive counter to just current amount, loop again.
159 }
160 } else {
161 // Lock acquired by other thread.
162 if (trylock) {
163 return Monitor::State::ILLEGAL;
164 }
165
166 // Retry acquiring light lock in loop first to avoid excessive inflation
167 static constexpr uint32_t MAX_TRYLOCK_RETRY = 100;
168 static constexpr uint32_t YIELD_AFTER = 50;
169
170 lightlockRetryCount++;
171 if (lightlockRetryCount < MAX_TRYLOCK_RETRY) {
172 if (lightlockRetryCount > YIELD_AFTER) {
173 MTManagedThread::Yield();
174 }
175 } else {
176 // Retried acquiring light lock for too long, do inflation
177
178 thread->SetEnterMonitorObject(objHandle.GetPtr());
179 thread->SetWaitingMonitorOldStatus(ThreadStatus::IS_WAITING_INFLATION);
180 Monitor::InflateThinLock(thread, objHandle);
181 #if defined(PANDA_USE_FUTEX)
182 lightlockRetryCount = 0;
183 #else
184 shouldInflate = true;
185 #endif
186 }
187 }
188 // Couldn't update mark.
189 if (trylock) {
190 return Monitor::State::ILLEGAL;
191 }
192
193 return std::nullopt;
194 }
195
HandleUnlockedState(MarkWord & mark,MTManagedThread * thread,VMHandle<ObjectHeader> & objHandle,bool & shouldInflate,bool trylock)196 std::optional<Monitor::State> Monitor::HandleUnlockedState(MarkWord &mark, MTManagedThread *thread,
197 VMHandle<ObjectHeader> &objHandle, bool &shouldInflate,
198 bool trylock)
199 {
200 if (shouldInflate) {
201 if (Monitor::Inflate(objHandle.GetPtr(), thread)) {
202 thread->PushLocalObjectLocked(objHandle.GetPtr());
203 return Monitor::State::OK;
204 }
205 // Couldn't inflate.
206 if (trylock) {
207 return Monitor::State::ILLEGAL;
208 }
209
210 return std::nullopt;
211 }
212
213 ASSERT(thread->GetInternalId() <= MarkWord::LIGHT_LOCK_THREADID_MAX_COUNT);
214 auto newMark = mark.DecodeFromLightLock(thread->GetInternalId(), 1);
215 // Strong CAS as the loop iteration is large
216 ASSERT(objHandle.GetPtr() != nullptr);
217 auto ret = objHandle.GetPtr()->AtomicSetMark(mark, newMark);
218 if (ret) {
219 LOG(DEBUG, RUNTIME) << "The lightweight monitor was successfully acquired for the first time";
220 Monitor::TraceMonitorLock(objHandle.GetPtr(), false);
221 thread->PushLocalObjectLocked(objHandle.GetPtr());
222 return Monitor::State::OK;
223 }
224 // Couldn't update mark.
225 if (trylock) {
226 return Monitor::State::ILLEGAL;
227 }
228
229 return std::nullopt;
230 }
231
HandleHeavyLockedState(Monitor * monitor,MTManagedThread * thread,VMHandle<ObjectHeader> & objHandle,bool trylock)232 Monitor::State Monitor::HandleHeavyLockedState(Monitor *monitor, MTManagedThread *thread,
233 VMHandle<ObjectHeader> &objHandle, bool trylock)
234 {
235 if (monitor == nullptr) {
236 // Not sure if it is possible
237 return Monitor::State::ILLEGAL;
238 }
239 bool ret = monitor->Acquire(thread, objHandle, trylock);
240 if (ret) {
241 thread->PushLocalObjectLocked(objHandle.GetPtr());
242 }
243 return ret ? Monitor::State::OK : Monitor::State::ILLEGAL;
244 }
245
246 /**
247 * Static call, which implements the basic functionality of monitors:
248 * heavyweight, lightweight and so on.
249 *
250 * @param obj an object header of corresponding object
251 * @param trylock is true if the function should fail in case of lock was already acquired by other thread
252 * @return state of function execution (ok, illegal)
253 */
MonitorEnter(ObjectHeader * obj,bool trylock)254 Monitor::State Monitor::MonitorEnter(ObjectHeader *obj, bool trylock)
255 {
256 auto *thread = MTManagedThread::GetCurrent();
257 ASSERT(thread != nullptr);
258 // This function can unlock MutatorLock, so GC can run during lock acquire waiting
259 // so we need to use handle to get updated header pointer
260 [[maybe_unused]] HandleScope<ObjectHeader *> scope(thread);
261 VMHandle<ObjectHeader> objHandle(thread, obj);
262 bool shouldInflate = false;
263 uint32_t lightlockRetryCount = 0;
264
265 while (true) {
266 MarkWord mark = objHandle.GetPtr()->AtomicGetMark();
267 MarkWord::ObjectState state = mark.GetState();
268
269 LOG(DEBUG, RUNTIME) << "Try to enter monitor " << std::hex << obj << " with state " << std::dec << state;
270
271 switch (state) {
272 case MarkWord::STATE_HEAVY_LOCKED: {
273 auto monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
274 return HandleHeavyLockedState(monitor, thread, objHandle, trylock);
275 }
276 case MarkWord::STATE_LIGHT_LOCKED: {
277 auto retState =
278 HandleLightLockedState(mark, thread, objHandle, lightlockRetryCount, shouldInflate, trylock);
279 if (retState.has_value()) {
280 return retState.value();
281 }
282 // Go to the next iteration
283 continue;
284 }
285 case MarkWord::STATE_HASHED:
286 if (Inflate(objHandle.GetPtr(), thread)) {
287 thread->PushLocalObjectLocked(objHandle.GetPtr());
288 return State::OK;
289 }
290 // Couldn't inflate.
291 if (trylock) {
292 return State::ILLEGAL;
293 }
294 // Go to the next iteration
295 continue;
296 case MarkWord::STATE_UNLOCKED: {
297 auto retState = HandleUnlockedState(mark, thread, objHandle, shouldInflate, trylock);
298 if (retState.has_value()) {
299 return retState.value();
300 }
301 // Go to the next iteration
302 continue;
303 }
304 case MarkWord::STATE_GC:
305 LOG(FATAL, RUNTIME) << "Not yet implemented";
306 return State::ILLEGAL;
307 default:
308 LOG(FATAL, RUNTIME) << "Undefined object state";
309 return State::ILLEGAL;
310 }
311 }
312 }
313
MonitorExit(ObjectHeader * obj)314 Monitor::State Monitor::MonitorExit(ObjectHeader *obj)
315 {
316 ASSERT(obj != nullptr);
317 auto thread = MTManagedThread::GetCurrent();
318 bool ret = false;
319
320 while (true) {
321 MarkWord mark = obj->AtomicGetMark();
322 MarkWord newMark = mark;
323 MarkWord::ObjectState state = mark.GetState();
324 LOG(DEBUG, RUNTIME) << "Try to exit monitor " << std::hex << obj << " with state " << std::dec << state;
325 switch (state) {
326 case MarkWord::STATE_HEAVY_LOCKED: {
327 auto monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
328 ret = monitor->Release(thread);
329 if (ret) {
330 thread->PopLocalObjectLocked(obj);
331 }
332 return ret ? State::OK : State::ILLEGAL;
333 }
334 case MarkWord::STATE_LIGHT_LOCKED: {
335 if (mark.GetThreadId() != thread->GetInternalId()) {
336 LOG(DEBUG, RUNTIME) << "Caling MonitorEnter on object which isn't owned by this thread";
337 return State::ILLEGAL;
338 }
339 uint32_t newCount = mark.GetLockCount() - 1;
340 if (newCount != 0) {
341 newMark = mark.DecodeFromLightLock(thread->GetInternalId(), newCount);
342 } else {
343 newMark = mark.DecodeFromUnlocked();
344 }
345 // Strong CAS as the loop iteration is large
346 if (obj->AtomicSetMark(mark, newMark)) {
347 LOG(DEBUG, RUNTIME) << "Exited lightweight lock";
348 TraceMonitorUnLock();
349 thread->PopLocalObjectLocked(obj);
350 return State::OK;
351 }
352 // CAS failed, must have been heavy locked by other thread. Retry unlock.
353 continue;
354 }
355 case MarkWord::STATE_HASHED:
356 case MarkWord::STATE_UNLOCKED:
357 LOG(ERROR, RUNTIME) << "Try to perform monitor exit from unlocked state";
358 return State::ILLEGAL;
359 case MarkWord::STATE_GC:
360 LOG(FATAL, RUNTIME) << "Not yet implemented";
361 return State::ILLEGAL;
362 default:
363 LOG(FATAL, RUNTIME) << "Undefined object state";
364 return State::ILLEGAL;
365 }
366 }
367 }
368
DoWaitInternal(MTManagedThread * thread,ThreadStatus status,uint64_t timeout,uint64_t nanos)369 static inline bool DoWaitInternal(MTManagedThread *thread, ThreadStatus status, uint64_t timeout, uint64_t nanos)
370 REQUIRES(*(thread->GetWaitingMutex()))
371 {
372 bool isTimeout = false;
373 if (timeout == 0 && nanos == 0) {
374 // Normal wait
375 thread->WaitWithLockHeld(status);
376 } else {
377 isTimeout = thread->TimedWaitWithLockHeld(status, timeout, nanos, false);
378 }
379 return isTimeout;
380 }
381
WaitWithHeavyLockedState(MTManagedThread * thread,VMHandle<ObjectHeader> & objHandle,MarkWord & mark,ThreadStatus status,uint64_t timeout,uint64_t nanos,bool ignoreInterruption)382 Monitor::State Monitor::WaitWithHeavyLockedState(MTManagedThread *thread, VMHandle<ObjectHeader> &objHandle,
383 MarkWord &mark, ThreadStatus status, uint64_t timeout, uint64_t nanos,
384 bool ignoreInterruption)
385 {
386 State resultState = State::OK;
387 auto monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
388 ASSERT(monitor != nullptr);
389 if (monitor->GetOwner() != thread) {
390 // The monitor is acquired by other thread
391 // throw an internal exception?
392 LOG(ERROR, RUNTIME) << "Illegal monitor state: try to wait with monitor acquired by other thread";
393 return State::ILLEGAL;
394 }
395
396 thread->GetWaitingMutex()->Lock();
397
398 // Use LockHolder inside scope
399 uint64_t counter = monitor->recursiveCounter_;
400 // Wait should be called under the monitor. We checked it in the previous if.
401 // Thus, the operation with queues are thread-safe
402 monitor->waiters_.PushFront(*thread);
403 thread->SetWaitingMonitor(monitor);
404 thread->SetWaitingMonitorOldStatus(status);
405
406 monitor->recursiveCounter_ = 1;
407 // Atomic with relaxed order reason: memory access in monitor
408 monitor->waitersCounter_.fetch_add(1, std::memory_order_relaxed);
409 monitor->Release(thread);
410
411 bool isTimeout = false;
412 if (thread->IsInterruptedWithLockHeld() && !ignoreInterruption) {
413 resultState = State::INTERRUPTED;
414 thread->GetWaitingMutex()->Unlock();
415 // Calling Safepoing to let GC start if needed
416 // in the else branch GC can start during Wait
417 ark::interpreter::RuntimeInterface::Safepoint();
418 } else {
419 TraceMonitorLock(objHandle.GetPtr(), true);
420 isTimeout = DoWaitInternal(thread, status, timeout, nanos);
421 TraceMonitorUnLock(); // End Wait().
422 thread->GetWaitingMutex()->Unlock();
423 }
424 // Unlock WaitingMutex before to avoid deadlock
425 // Nothing happen, if the thread is rescheduled between,
426 // As the monitor was already released for external users
427 [[maybe_unused]] bool ret = monitor->Acquire(thread, objHandle, false);
428 ASSERT(ret);
429 // Atomic with relaxed order reason: memory access in monitor
430 monitor->waitersCounter_.fetch_sub(1, std::memory_order_relaxed);
431 monitor->recursiveCounter_ = counter;
432
433 if (thread->IsInterrupted()) {
434 // NOTE(dtrubenkov): call ark::ThrowException when it will be imlemented
435 resultState = State::INTERRUPTED;
436 }
437
438 // problems with equality of MTManagedThread's
439 bool found = monitor->waiters_.RemoveIf(
440 [thread](MTManagedThread &t) { return thread->GetInternalId() == t.GetInternalId(); });
441 // If no matching thread found in waiters_, it should have been moved to to_wakeup_
442 // but this thread timed out or got interrupted
443 if (!found) {
444 monitor->toWakeup_.RemoveIf(
445 [thread](MTManagedThread &t) { return thread->GetInternalId() == t.GetInternalId(); });
446 }
447
448 thread->SetWaitingMonitor(nullptr);
449 thread->SetWaitingMonitorOldStatus(ThreadStatus::FINISHED);
450 Runtime::GetCurrent()->GetNotificationManager()->MonitorWaitedEvent(objHandle.GetPtr(), isTimeout);
451
452 return resultState;
453 }
454
455 /** Zero timeout is used as infinite wait (see docs)
456 */
Wait(ObjectHeader * obj,ThreadStatus status,uint64_t timeout,uint64_t nanos,bool ignoreInterruption)457 Monitor::State Monitor::Wait(ObjectHeader *obj, ThreadStatus status, uint64_t timeout, uint64_t nanos,
458 bool ignoreInterruption)
459 {
460 ASSERT(obj != nullptr);
461 auto *thread = MTManagedThread::GetCurrent();
462 ASSERT(thread != nullptr);
463 State resultState = State::OK;
464
465 // This function can unlock MutatorLock, so GC can run during wait
466 // so we need to use handle to get updated header pointer
467 [[maybe_unused]] HandleScope<ObjectHeader *> scope(thread);
468 VMHandle<ObjectHeader> objHandle(thread, obj);
469
470 Runtime::GetCurrent()->GetNotificationManager()->MonitorWaitEvent(obj, timeout);
471
472 while (true) {
473 MarkWord mark = objHandle->AtomicGetMark();
474 MarkWord::ObjectState state = mark.GetState();
475 LOG(DEBUG, RUNTIME) << "Try to wait with state " << state;
476 switch (state) {
477 case MarkWord::STATE_HEAVY_LOCKED: {
478 return WaitWithHeavyLockedState(thread, objHandle, mark, status, timeout, nanos, ignoreInterruption);
479 }
480 case MarkWord::STATE_LIGHT_LOCKED:
481 if (mark.GetThreadId() != thread->GetInternalId()) {
482 LOG(FATAL, RUNTIME) << "Illegal monitor state: try to wait with monitor acquired by other thread";
483 return resultState;
484 }
485 Inflate(objHandle.GetPtr(), thread);
486 // Go to the next iteration.
487 continue;
488 case MarkWord::STATE_UNLOCKED:
489 case MarkWord::STATE_HASHED:
490 case MarkWord::STATE_GC:
491 LOG(ERROR, RUNTIME) << "Try to perform Wait from unsupported state";
492 return State::ILLEGAL;
493 default:
494 LOG(FATAL, RUNTIME) << "Undefined object state";
495 UNREACHABLE();
496 }
497 }
498 }
499
Notify(ObjectHeader * obj)500 Monitor::State Monitor::Notify(ObjectHeader *obj)
501 {
502 ASSERT(obj != nullptr);
503 MarkWord mark = obj->AtomicGetMark();
504 MarkWord::ObjectState state = mark.GetState();
505 auto thread = MTManagedThread::GetCurrent();
506 LOG(DEBUG, RUNTIME) << "Try to notify with state " << state;
507
508 switch (state) {
509 case MarkWord::STATE_HEAVY_LOCKED: {
510 auto monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
511 ASSERT(monitor != nullptr);
512 if (monitor->GetOwner() != thread) {
513 // The monitor is acquired by other thread
514 // throw an internal exception?
515 LOG(ERROR, RUNTIME) << "Illegal monitor state: try to notify with monitor acquired by other thread";
516 return State::ILLEGAL;
517 }
518
519 // Notify should be called under the monitor. We checked it in the previous if.
520 // Thus, the operation with queues are thread-safe
521
522 // Move one thread from waiters to wake_up
523 if (!monitor->waiters_.Empty()) {
524 // With current ark::List implementation this reference is valid.
525 // This can be broken with future changes.
526 auto &waiter = monitor->waiters_.Front();
527 monitor->waiters_.PopFront();
528 monitor->toWakeup_.PushFront(waiter);
529 }
530 return State::OK; // Success
531 }
532 case MarkWord::STATE_LIGHT_LOCKED:
533 if (mark.GetThreadId() != thread->GetInternalId()) {
534 LOG(ERROR, RUNTIME) << "Illegal monitor state: try to notify with monitor acquired by other thread";
535 return State::ILLEGAL;
536 }
537 return State::OK; // Success
538 case MarkWord::STATE_UNLOCKED:
539 case MarkWord::STATE_HASHED:
540 case MarkWord::STATE_GC:
541 LOG(ERROR, RUNTIME) << "Try to perform Notify from unsupported state";
542 return State::ILLEGAL;
543 default:
544 LOG(FATAL, RUNTIME) << "Undefined object state";
545 UNREACHABLE();
546 }
547 }
548
NotifyAll(ObjectHeader * obj)549 Monitor::State Monitor::NotifyAll(ObjectHeader *obj)
550 {
551 ASSERT(obj != nullptr);
552 MarkWord mark = obj->AtomicGetMark();
553 MarkWord::ObjectState state = mark.GetState();
554 auto thread = MTManagedThread::GetCurrent();
555 LOG(DEBUG, RUNTIME) << "Try to notify all with state " << state;
556
557 switch (state) {
558 case MarkWord::STATE_HEAVY_LOCKED: {
559 auto monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
560 if (monitor->GetOwner() != thread) {
561 // The monitor is acquired by other thread
562 // throw an internal exception?
563 LOG(ERROR, RUNTIME) << "Illegal monitor state: try to notify with monitor acquired by other thread";
564 return State::ILLEGAL;
565 }
566
567 // NotifyAll should be called under the monitor. We checked it in the previous if.
568 // Thus, the operation with queues are thread-safe
569 if (monitor->toWakeup_.Empty()) {
570 monitor->toWakeup_.Swap(monitor->waiters_);
571 return State::OK;
572 }
573
574 // Concatenate two queues
575 if (!monitor->waiters_.Empty()) {
576 monitor->toWakeup_.Splice(monitor->waiters_);
577 monitor->waiters_.Clear();
578 }
579 return State::OK; // Success
580 }
581 case MarkWord::STATE_LIGHT_LOCKED:
582 if (mark.GetThreadId() != thread->GetInternalId()) {
583 LOG(ERROR, RUNTIME) << "Illegal monitor state: try to notify with monitor acquired by other thread";
584 return State::ILLEGAL;
585 }
586 return State::OK; // Success
587 case MarkWord::STATE_UNLOCKED:
588 case MarkWord::STATE_HASHED:
589 case MarkWord::STATE_GC:
590 LOG(ERROR, RUNTIME) << "Try to perform NotifyAll from unsupported state";
591 return State::ILLEGAL;
592 default:
593 LOG(FATAL, RUNTIME) << "Undefined object state";
594 UNREACHABLE();
595 }
596 }
597
Acquire(MTManagedThread * thread,const VMHandle<ObjectHeader> & objHandle)598 void Monitor::Acquire(MTManagedThread *thread, const VMHandle<ObjectHeader> &objHandle)
599 {
600 Runtime::GetCurrent()->GetNotificationManager()->MonitorContendedEnterEvent(objHandle.GetPtr());
601 // If not trylock...
602 // Do atomic add out of scope to prevent GC getting old waiters_counter_
603 // Atomic with relaxed order reason: memory access in monitor
604 waitersCounter_.fetch_add(1, std::memory_order_relaxed);
605 thread->SetEnterMonitorObject(objHandle.GetPtr());
606 thread->SetWaitingMonitorOldStatus(ThreadStatus::IS_BLOCKED);
607 {
608 ScopedChangeThreadStatus ets(thread, ThreadStatus::IS_BLOCKED);
609 // Save current monitor, on which the given thread is blocked.
610 // It can be used to detect potential deadlock with daemon threds.
611 thread->SetEnteringMonitor(this);
612 lock_.Lock();
613 // Deadlock is no longer possible with the given thread.
614 thread->SetEnteringMonitor(nullptr);
615 // Do this inside scope for thread to release this monitor during runtime destroy
616 if (!this->SetOwner(nullptr, thread)) {
617 LOG(FATAL, RUNTIME) << "Set monitor owner failed in Acquire";
618 }
619 thread->AddMonitor(this);
620 this->recursiveCounter_++;
621 }
622 thread->SetEnterMonitorObject(nullptr);
623 thread->SetWaitingMonitorOldStatus(ThreadStatus::FINISHED);
624 // Atomic with relaxed order reason: memory access in monitor
625 waitersCounter_.fetch_sub(1, std::memory_order_relaxed);
626 // Even thout these 2 warnings are valid, We suppress them. Reason is to have consistent logging
627 // Otherwise we would see that lock was done on one monitor address,
628 // and unlock (after GC) - ona different one
629 // SUPPRESS_CSA_NEXTLINE(alpha.core.WasteObjHeader)
630 Runtime::GetCurrent()->GetNotificationManager()->MonitorContendedEnteredEvent(objHandle.GetPtr());
631 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully acquired for the first time";
632 // SUPPRESS_CSA_NEXTLINE(alpha.core.WasteObjHeader)
633 TraceMonitorLock(objHandle.GetPtr(), false);
634 }
635
Acquire(MTManagedThread * thread,const VMHandle<ObjectHeader> & objHandle,bool trylock)636 bool Monitor::Acquire(MTManagedThread *thread, const VMHandle<ObjectHeader> &objHandle, bool trylock)
637 {
638 ASSERT_MANAGED_CODE();
639
640 MTManagedThread *owner = this->GetOwner();
641 if (owner == thread) {
642 // Do we need to hold a lock here?
643 this->recursiveCounter_++;
644 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully recursively acquired";
645 TraceMonitorLock(objHandle.GetPtr(), false);
646 return true;
647 }
648
649 // Use trylock first
650 if (trylock) {
651 if (!lock_.TryLock()) {
652 return false;
653 }
654 } else {
655 #ifdef PANDA_USE_FUTEX
656 if (!lock_.TryLockWithSpinning()) {
657 #else
658 if (!lock_.TryLock()) {
659 #endif // PANDA_USE_FUTEX
660 Acquire(thread, objHandle);
661 return true;
662 }
663 }
664
665 if (!this->SetOwner(nullptr, thread)) {
666 LOG(FATAL, RUNTIME) << "Set monitor owner failed in Acquire";
667 }
668 thread->AddMonitor(this);
669 this->recursiveCounter_++;
670 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully acquired for the first time";
671 TraceMonitorLock(objHandle.GetPtr(), false);
672 return true;
673 }
674
675 void Monitor::InitWithOwner(MTManagedThread *thread, ObjectHeader *obj)
676 {
677 ASSERT(this->GetOwner() == nullptr);
678
679 #ifdef PANDA_USE_FUTEX
680 ASSERT(thread == MTManagedThread::GetCurrent() || thread->GetStatus() != ThreadStatus::RUNNING);
681 lock_.LockForOther(thread->GetId());
682 #else
683 ASSERT(thread == MTManagedThread::GetCurrent());
684 [[maybe_unused]] bool res = lock_.TryLock();
685 ASSERT(res);
686 #endif // PANDA_USE_FUTEX
687
688 if (!this->SetOwner(nullptr, thread)) {
689 LOG(FATAL, RUNTIME) << "Set monitor owner failed in InitWithOwner";
690 }
691 this->recursiveCounter_++;
692 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully initialized for the first time";
693 TraceMonitorLock(obj, false);
694 }
695
696 void Monitor::ReleaseOnFailedInflate(MTManagedThread *thread)
697 {
698 if (thread != this->GetOwner()) {
699 LOG(FATAL, RUNTIME) << "Releasing lock which isn't owned by this thread";
700 }
701 TraceMonitorUnLock();
702 this->recursiveCounter_--;
703 ASSERT(this->recursiveCounter_ == 0);
704 // This should never fail
705 [[maybe_unused]] bool success = this->SetOwner(thread, nullptr);
706 ASSERT(success);
707 #ifdef PANDA_USE_FUTEX
708 ASSERT(thread == MTManagedThread::GetCurrent() || thread->GetStatus() != ThreadStatus::RUNNING);
709 lock_.UnlockForOther(thread->GetId());
710 #else
711 ASSERT(thread == MTManagedThread::GetCurrent());
712 lock_.Unlock();
713 #endif // PANDA_USE_FUTEX
714 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully released after failed inflation";
715 }
716
717 bool Monitor::Release(MTManagedThread *thread)
718 {
719 if (thread != this->GetOwner()) {
720 LOG(FATAL, RUNTIME) << "Releasing lock which isn't owned by this thread";
721 return false;
722 }
723 TraceMonitorUnLock();
724 this->recursiveCounter_--;
725 if (this->recursiveCounter_ == 0) {
726 if (!this->SetOwner(thread, nullptr)) {
727 LOG(FATAL, RUNTIME) << "Set monitor owner failed in Release";
728 }
729 // Signal the only waiter (the other one will be signaled after the next release)
730 MTManagedThread *waiter = nullptr;
731 Monitor *waitingMon = nullptr;
732 if (!this->toWakeup_.Empty()) {
733 // NB! Current list implementation leaves this pointer valid after PopFront, change this
734 // if List implementation is changed.
735 waiter = &(this->toWakeup_.Front());
736 waitingMon = waiter->GetWaitingMonitor();
737 this->toWakeup_.PopFront();
738 }
739 thread->RemoveMonitor(this);
740 // Signal waiter after mutex unlock so that signalled thread doesn't get stuck on lock_
741 if (waiter != nullptr && waitingMon == this) {
742 waiter->Signal();
743 LOG(DEBUG, RUNTIME) << "Send the notifing signal to " << waiter->GetId();
744 }
745 lock_.Unlock();
746 }
747 LOG(DEBUG, RUNTIME) << "The fat monitor was successfully released";
748 return true;
749 }
750
751 bool Monitor::TryAddMonitor(Monitor *monitor, MarkWord &oldMark, MTManagedThread *thread, ObjectHeader *obj,
752 MonitorPool *monitorPool)
753 {
754 MarkWord newMark = oldMark.DecodeFromMonitor(monitor->GetId());
755 bool ret = obj->AtomicSetMark(oldMark, newMark);
756 if (!ret) {
757 // Means, someone changed the mark
758 monitor->recursiveCounter_ = 1;
759 monitor->ReleaseOnFailedInflate(thread);
760 monitorPool->FreeMonitor(monitor->GetId());
761 } else {
762 // Unlike normal Acquire, AddMonitor should be done not in InitWithOwner but after successful inflation to avoid
763 // data race
764 thread->AddMonitor(monitor);
765 }
766
767 return ret;
768 }
769
770 template <bool FOR_OTHER_THREAD>
771 bool Monitor::Inflate(ObjectHeader *obj, MTManagedThread *thread)
772 {
773 ASSERT(obj != nullptr);
774 MarkWord oldMark = obj->AtomicGetMark();
775 MarkWord::ObjectState state = oldMark.GetState();
776
777 // Dont inflate if someone already inflated the lock.
778 if (state == MarkWord::STATE_HEAVY_LOCKED) {
779 return false;
780 }
781 // NOLINTNEXTLINE(readability-braces-around-statements, hicpp-braces-around-statements)
782 if constexpr (FOR_OTHER_THREAD) { // NOLINT(bugprone-suspicious-semicolon)
783 // Dont inflate if monitor got unlocked or acquired by other thread.
784 if (state != MarkWord::STATE_LIGHT_LOCKED || oldMark.GetThreadId() != thread->GetInternalId()) {
785 return false;
786 }
787 }
788
789 auto *monitorPool = thread->GetMonitorPool();
790 auto *monitor = monitorPool->CreateMonitor(obj);
791 if (monitor == nullptr) {
792 LOG(FATAL, RUNTIME) << "Couldn't create new monitor. Out of memory?";
793 return false;
794 }
795 monitor->InitWithOwner(thread, obj);
796
797 switch (state) {
798 case MarkWord::STATE_LIGHT_LOCKED:
799 if (oldMark.GetThreadId() != thread->GetInternalId()) {
800 monitor->ReleaseOnFailedInflate(thread);
801 monitorPool->FreeMonitor(monitor->GetId());
802 return false;
803 }
804 monitor->recursiveCounter_ = oldMark.GetLockCount();
805 break;
806 case MarkWord::STATE_HASHED:
807 monitor->SetHashCode(oldMark.GetHash());
808 /* fallthrough */
809 [[fallthrough]];
810 case MarkWord::STATE_UNLOCKED:
811 // NOLINTNEXTLINE(readability-braces-around-statements, hicpp-braces-around-statements)
812 if constexpr (FOR_OTHER_THREAD) { // NOLINT(bugprone-suspicious-semicolon)
813 // We did check above, has to be unreachable
814 UNREACHABLE();
815 } else { // NOLINT(readability-misleading-indentation)
816 break;
817 }
818 case MarkWord::STATE_HEAVY_LOCKED:
819 // Has to be unreachable
820 UNREACHABLE();
821 case MarkWord::STATE_GC:
822 LOG(FATAL, RUNTIME) << "Trying to inflate object in GC state";
823 return false;
824 default:
825 LOG(FATAL, RUNTIME) << "Undefined object state";
826 return false;
827 }
828
829 return TryAddMonitor(monitor, oldMark, thread, obj, monitorPool);
830 }
831
832 bool Monitor::Deflate(ObjectHeader *obj)
833 {
834 Monitor *monitor = nullptr;
835 MarkWord oldMark = obj->AtomicGetMark();
836 MarkWord::ObjectState state = oldMark.GetState();
837 bool ret = false;
838
839 if (state != MarkWord::STATE_HEAVY_LOCKED) {
840 LOG(DEBUG, RUNTIME) << "Trying to deflate non-heavy locked object";
841 return false;
842 }
843 auto *thread = MTManagedThread::GetCurrent();
844 ASSERT(thread != nullptr);
845 auto *monitorPool = thread->GetMonitorPool();
846 monitor = monitorPool->LookupMonitor(oldMark.GetMonitorId());
847 if (monitor == nullptr) {
848 LOG(DEBUG, RUNTIME) << "Monitor was already destroyed by someone else.";
849 return false;
850 }
851
852 ret = monitor->DeflateInternal();
853 if (ret) {
854 monitorPool->FreeMonitor(monitor->GetId());
855 }
856 return ret;
857 }
858
859 bool Monitor::DeflateInternal()
860 {
861 if (GetOwner() != nullptr) {
862 LOG(DEBUG, RUNTIME) << "Trying to deflate monitor which already has owner";
863 return false;
864 }
865 // Atomic with relaxed order reason: memory access in monitor
866 if (waitersCounter_.load(std::memory_order_relaxed) > 0) {
867 LOG(DEBUG, RUNTIME) << "Trying to deflate monitor which is trying to be acquired by other threads";
868 return false;
869 }
870 if (!lock_.TryLock()) {
871 LOG(DEBUG, RUNTIME) << "Couldn't TryLock monitor for deflation";
872 return false;
873 }
874 ASSERT(obj_ != nullptr);
875 ASSERT(recursiveCounter_ == 0);
876 ASSERT(waiters_.Empty());
877 ASSERT(toWakeup_.Empty());
878 ASSERT(GetOwner() == static_cast<MTManagedThread *>(nullptr));
879 MarkWord oldMark = obj_->AtomicGetMark();
880 MarkWord newMark = oldMark;
881 if (HasHashCode()) {
882 newMark = oldMark.DecodeFromHash(GetHashCode());
883 LOG(DEBUG, RUNTIME) << "Deflating monitor to hash";
884 } else {
885 newMark = oldMark.DecodeFromUnlocked();
886 LOG(DEBUG, RUNTIME) << "Deflating monitor to unlocked";
887 }
888
889 // Warning: AtomicSetMark is weak, retry
890 while (!obj_->AtomicSetMark<false>(oldMark, newMark)) {
891 newMark = HasHashCode() ? oldMark.DecodeFromHash(GetHashCode()) : oldMark.DecodeFromUnlocked();
892 }
893 lock_.Unlock();
894 return true;
895 }
896
897 uint8_t Monitor::HoldsLock(ObjectHeader *obj)
898 {
899 MarkWord mark = obj->AtomicGetMark();
900 MarkWord::ObjectState state = mark.GetState();
901 MTManagedThread *thread = MTManagedThread::GetCurrent();
902 ASSERT(thread != nullptr);
903
904 switch (state) {
905 case MarkWord::STATE_HEAVY_LOCKED: {
906 Monitor *monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
907 ASSERT(monitor != nullptr);
908 // asm has no boolean type
909 return (monitor->GetOwner() == thread) ? 1 : 0;
910 }
911 case MarkWord::STATE_LIGHT_LOCKED:
912 return (mark.GetThreadId() == thread->GetInternalId()) ? 1 : 0;
913 case MarkWord::STATE_UNLOCKED:
914 case MarkWord::STATE_HASHED:
915 case MarkWord::STATE_GC:
916 return 0;
917 default:
918 LOG(FATAL, RUNTIME) << "Undefined object state";
919 return 0;
920 }
921 }
922
923 uint32_t Monitor::GetLockOwnerOsThreadID(ObjectHeader *obj)
924 {
925 if (obj == nullptr) {
926 return MTManagedThread::NON_INITIALIZED_THREAD_ID;
927 }
928 MarkWord mark = obj->AtomicGetMark();
929 MarkWord::ObjectState state = mark.GetState();
930
931 switch (state) {
932 case MarkWord::STATE_HEAVY_LOCKED: {
933 auto *thread = MTManagedThread::GetCurrent();
934 ASSERT(thread != nullptr);
935 Monitor *monitor = thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
936 ASSERT(monitor != nullptr);
937 MTManagedThread *owner = monitor->GetOwner();
938 if (owner == nullptr) {
939 return MTManagedThread::NON_INITIALIZED_THREAD_ID;
940 }
941 return owner->GetId();
942 }
943 case MarkWord::STATE_LIGHT_LOCKED: {
944 return mark.GetThreadId();
945 }
946 case MarkWord::STATE_UNLOCKED:
947 case MarkWord::STATE_HASHED:
948 case MarkWord::STATE_GC:
949 return 0;
950 default:
951 LOG(FATAL, RUNTIME) << "Undefined object state";
952 return 0;
953 }
954 }
955
956 Monitor *Monitor::GetMonitorFromObject(ObjectHeader *obj)
957 {
958 if (obj != nullptr) {
959 MarkWord mark = obj->AtomicGetMark();
960 MarkWord::ObjectState state = mark.GetState();
961 auto *thread = MTManagedThread::GetCurrent();
962 ASSERT(thread != nullptr);
963 switch (state) {
964 case MarkWord::STATE_HEAVY_LOCKED:
965 return thread->GetMonitorPool()->LookupMonitor(mark.GetMonitorId());
966 case MarkWord::STATE_LIGHT_LOCKED:
967 return nullptr;
968 default:
969 // Shouldn't happen, return nullptr
970 LOG(WARNING, RUNTIME) << "obj:" << obj << " not locked by heavy or light locked";
971 }
972 }
973 return nullptr;
974 }
975
976 inline void Monitor::TraceMonitorLock([[maybe_unused]] ObjectHeader *obj, bool isWait)
977 {
978 if (UNLIKELY(ark::trace::IsEnabled())) {
979 // Use stack memory to avoid "Too many allocations" error.
980 constexpr int BUF_SIZE = 32;
981 std::array<char, BUF_SIZE> buf = {};
982 // For Security reasons, optionally hide address based on build target
983 #if !defined(PANDA_TARGET_OHOS) || !defined(NDEBUG)
984 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
985 int ret = snprintf_s(buf.data(), BUF_SIZE, BUF_SIZE - 1,
986 (isWait ? "Waiting on 0x%" PRIxPTR : "Locking 0x%" PRIxPTR), ToUintPtr(obj));
987 #else
988 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
989 int ret = snprintf_s(buf.data(), BUF_SIZE, BUF_SIZE - 1, (isWait ? "Waiting on object" : "Locking object"));
990 #endif
991 if (ret < 0) {
992 UNREACHABLE();
993 }
994 trace::BeginTracePoint(buf.data());
995 }
996 }
997
998 inline void Monitor::TraceMonitorUnLock()
999 {
1000 if (UNLIKELY(ark::trace::IsEnabled())) {
1001 trace::EndTracePoint();
1002 }
1003 }
1004
1005 uint32_t Monitor::GetHashCode()
1006 {
1007 while (!HasHashCode()) {
1008 uint32_t expected = 0;
1009 uint32_t newHash = ObjectHeader::GenerateHashCode();
1010 if (hashCode_.compare_exchange_weak(expected, newHash)) {
1011 return newHash;
1012 }
1013 }
1014 ASSERT(HasHashCode());
1015 // Atomic with relaxed order reason: memory access in monitor
1016 return hashCode_.load(std::memory_order_relaxed);
1017 }
1018
1019 bool Monitor::HasHashCode() const
1020 {
1021 // Atomic with relaxed order reason: memory access in monitor
1022 return hashCode_.load(std::memory_order_relaxed) != 0;
1023 }
1024
1025 void Monitor::SetHashCode(uint32_t hash)
1026 {
1027 ASSERT(GetOwner() == MTManagedThread::GetCurrent());
1028 if (!HasHashCode()) {
1029 // Atomic with relaxed order reason: memory access in monitor
1030 hashCode_.store(hash, std::memory_order_relaxed);
1031 } else {
1032 LOG(FATAL, RUNTIME) << "Attempt to rewrite hash in monitor";
1033 }
1034 }
1035 } // namespace ark
1036