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