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