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