JSRunLoopTimer.cpp [plain text]
#include "config.h"
#include "JSRunLoopTimer.h"
#include "IncrementalSweeper.h"
#include "VM.h"
#include <mutex>
#include <wtf/NoTailCalls.h>
#if USE(GLIB_EVENT_LOOP)
#include <glib.h>
#include <wtf/glib/RunLoopSourcePriority.h>
#endif
namespace JSC {
static inline JSRunLoopTimer::Manager::EpochTime epochTime(Seconds delay)
{
return MonotonicTime::now().secondsSinceEpoch() + delay;
}
JSRunLoopTimer::Manager::PerVMData::PerVMData(Manager& manager, RunLoop& runLoop)
: runLoop(runLoop)
, timer(makeUnique<RunLoop::Timer<Manager>>(runLoop, &manager, &JSRunLoopTimer::Manager::timerDidFireCallback))
{
#if USE(GLIB_EVENT_LOOP)
timer->setPriority(RunLoopSourcePriority::JavascriptTimer);
timer->setName("[JavaScriptCore] JSRunLoopTimer");
#endif
}
void JSRunLoopTimer::Manager::timerDidFireCallback()
{
timerDidFire();
}
JSRunLoopTimer::Manager::PerVMData::~PerVMData()
{
runLoop->dispatch([timer = WTFMove(timer)] {
});
}
void JSRunLoopTimer::Manager::timerDidFire()
{
Vector<Ref<JSRunLoopTimer>> timersToFire;
{
auto locker = holdLock(m_lock);
RunLoop* currentRunLoop = &RunLoop::current();
EpochTime nowEpochTime = epochTime(0_s);
for (auto& entry : m_mapping) {
PerVMData& data = *entry.value;
if (data.runLoop.ptr() != currentRunLoop)
continue;
EpochTime scheduleTime = epochTime(s_decade);
for (size_t i = 0; i < data.timers.size(); ++i) {
{
auto& pair = data.timers[i];
if (pair.second > nowEpochTime) {
scheduleTime = std::min(pair.second, scheduleTime);
continue;
}
auto& last = data.timers.last();
if (&last != &pair)
std::swap(pair, last);
--i;
}
auto pair = data.timers.takeLast();
timersToFire.append(WTFMove(pair.first));
}
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
}
}
for (auto& timer : timersToFire)
timer->timerDidFire();
}
JSRunLoopTimer::Manager& JSRunLoopTimer::Manager::shared()
{
static Manager* manager;
static std::once_flag once;
std::call_once(once, [&] {
manager = new Manager;
});
return *manager;
}
void JSRunLoopTimer::Manager::registerVM(VM& vm)
{
auto data = makeUnique<PerVMData>(*this, vm.runLoop());
auto locker = holdLock(m_lock);
auto addResult = m_mapping.add({ vm.apiLock() }, WTFMove(data));
RELEASE_ASSERT(addResult.isNewEntry);
}
void JSRunLoopTimer::Manager::unregisterVM(VM& vm)
{
auto locker = holdLock(m_lock);
auto iter = m_mapping.find({ vm.apiLock() });
RELEASE_ASSERT(iter != m_mapping.end());
m_mapping.remove(iter);
}
void JSRunLoopTimer::Manager::scheduleTimer(JSRunLoopTimer& timer, Seconds delay)
{
EpochTime fireEpochTime = epochTime(delay);
auto locker = holdLock(m_lock);
auto iter = m_mapping.find(timer.m_apiLock);
RELEASE_ASSERT(iter != m_mapping.end());
PerVMData& data = *iter->value;
EpochTime scheduleTime = fireEpochTime;
bool found = false;
for (auto& entry : data.timers) {
if (entry.first.ptr() == &timer) {
entry.second = fireEpochTime;
found = true;
}
scheduleTime = std::min(scheduleTime, entry.second);
}
if (!found)
data.timers.append({ timer, fireEpochTime });
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
}
void JSRunLoopTimer::Manager::cancelTimer(JSRunLoopTimer& timer)
{
auto locker = holdLock(m_lock);
auto iter = m_mapping.find(timer.m_apiLock);
if (iter == m_mapping.end()) {
return;
}
PerVMData& data = *iter->value;
EpochTime scheduleTime = epochTime(s_decade);
for (unsigned i = 0; i < data.timers.size(); ++i) {
{
auto& entry = data.timers[i];
if (entry.first.ptr() == &timer) {
RELEASE_ASSERT(timer.refCount() >= 2); auto& last = data.timers.last();
if (&last != &entry)
std::swap(entry, last);
data.timers.removeLast();
i--;
continue;
}
}
scheduleTime = std::min(scheduleTime, data.timers[i].second);
}
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
}
Optional<Seconds> JSRunLoopTimer::Manager::timeUntilFire(JSRunLoopTimer& timer)
{
auto locker = holdLock(m_lock);
auto iter = m_mapping.find(timer.m_apiLock);
RELEASE_ASSERT(iter != m_mapping.end());
PerVMData& data = *iter->value;
for (auto& entry : data.timers) {
if (entry.first.ptr() == &timer) {
EpochTime nowEpochTime = epochTime(0_s);
return entry.second - nowEpochTime;
}
}
return WTF::nullopt;
}
void JSRunLoopTimer::timerDidFire()
{
NO_TAIL_CALLS();
{
auto locker = holdLock(m_lock);
if (!m_isScheduled) {
return;
}
}
auto locker = holdLock(m_apiLock.get());
RefPtr<VM> vm = m_apiLock->vm();
if (!vm) {
return;
}
doWork(*vm);
}
JSRunLoopTimer::JSRunLoopTimer(VM& vm)
: m_apiLock(vm.apiLock())
{
}
JSRunLoopTimer::~JSRunLoopTimer()
{
}
Optional<Seconds> JSRunLoopTimer::timeUntilFire()
{
return Manager::shared().timeUntilFire(*this);
}
void JSRunLoopTimer::setTimeUntilFire(Seconds intervalInSeconds)
{
{
auto locker = holdLock(m_lock);
m_isScheduled = true;
Manager::shared().scheduleTimer(*this, intervalInSeconds);
}
auto locker = holdLock(m_timerCallbacksLock);
for (auto& task : m_timerSetCallbacks)
task->run();
}
void JSRunLoopTimer::cancelTimer()
{
auto locker = holdLock(m_lock);
m_isScheduled = false;
Manager::shared().cancelTimer(*this);
}
void JSRunLoopTimer::addTimerSetNotification(TimerNotificationCallback callback)
{
auto locker = holdLock(m_timerCallbacksLock);
m_timerSetCallbacks.add(callback);
}
void JSRunLoopTimer::removeTimerSetNotification(TimerNotificationCallback callback)
{
auto locker = holdLock(m_timerCallbacksLock);
m_timerSetCallbacks.remove(callback);
}
}