/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gc/LightLock.h"
#include "mozilla/Atomics.h"
#include "mozilla/TimeStamp.h"
#include <thread>
#include "gc/GCRuntime.h"
#include "threading/LockGuard.h"
#include "util/WindowsWrapper.h"
#include "vm/MutexIDs.h"
#include "vm/Runtime.h"
using
namespace js;
#ifdef DEBUG
// Only one LightLock may be held by a thread at any time to prevent deadlock.
MOZ_THREAD_LOCAL(
bool) js::TlsLightLockHeld;
#endif
js::LightLockRuntime::LightLockRuntime() : mutex(mutexid::GCLightLock) {
#ifdef DEBUG
TlsLightLockHeld.infallibleInit();
TlsLightLockHeld.set(
false);
#endif
}
/* static */
LightLockRuntime* js::LightLockRuntime::from(JSRuntime* runtime) {
return &runtime->gc.lightLockRuntime;
}
MOZ_NEVER_INLINE
void js::LightLock::lockSlow(JSRuntime* runtime) {
uint32_t spinCounter =
0;
for (;;) {
uint32_t currentState = state;
// No one else can be waiting yet.
MOZ_ASSERT(!(currentState & HasWaiter));
// If the mutex is unlocked, attempt to lock it.
if (currentState == UnlockedState &&
state.compareExchange(UnlockedState, LockedState)) {
break;
}
// It's locked. Try spinning a few times.
if (spin(spinCounter)) {
continue;
}
// Otherwise wait on the underlying condition variable.
if (tryBlockUntilWoken(runtime)) {
break;
}
// If the mutex was unlocked in the meantime, restart.
spinCounter =
0;
}
MOZ_ASSERT(isLocked());
}
bool js::LightLock::tryBlockUntilWoken(JSRuntime* runtime) {
LightLockRuntime* llrt = LightLockRuntime::from(runtime);
LockGuard<Mutex> lock(llrt->mutex);
if (!state.compareExchange(LockedState, LockedWithWaiterState)) {
return false;
// State changed while we waited for the mutex.
}
bool waiting = true;
MOZ_ASSERT(!llrt->waitingPtr);
llrt->waitingPtr = &waiting;
auto wasWoken = [&]() {
// Check whether wakeOtherThread cleared |waitingPtr|.
return !waiting;
};
#ifdef DEBUG
mozilla::TimeDuration duration = mozilla::TimeDuration::FromSeconds(
10.
0);
if (!llrt->condVar.wait_for(lock, duration, wasWoken)) {
MOZ_CRASH_UNSAFE_PRINTF(
"Timeout waiting on LightLock in state %u\n",
unsigned(state));
}
#else
llrt->condVar.wait(lock, wasWoken);
#endif
return true;
}
MOZ_NEVER_INLINE
void js::LightLock::unlockSlow(JSRuntime* runtime) {
MOZ_ASSERT(hasWaiter());
LightLockRuntime* llrt = LightLockRuntime::from(runtime);
LockGuard<Mutex> lock(llrt->mutex);
// Hand over the lock to the waiting thread: clear the waiting state and wake
// it up.
MOZ_ALWAYS_TRUE(state.compareExchange(LockedWithWaiterState, LockedState));
wakeOtherThread(runtime, lock);
}
void js::LightLock::wakeOtherThread(JSRuntime* runtime,
const LockGuard<Mutex>& lock) {
LightLockRuntime* llrt = LightLockRuntime::from(runtime);
MOZ_ASSERT(llrt->waitingPtr);
MOZ_ASSERT(*llrt->waitingPtr);
*llrt->waitingPtr =
false;
llrt->waitingPtr = nullptr;
llrt->condVar.notify_one();
}
bool js::LightLock::spin(uint32_t& counter) {
if (counter >=
10) {
return false;
}
counter++;
if (counter <=
3) {
mozilla::cpu_pause();
}
else {
std::this_thread::yield();
}
return true;
}