blob: 2a0c06c5d23658fdfcca532a6640ffc2dadcaa3c [file] [log] [blame]
/*
* Copyright (C) 2012-2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSRunLoopTimer.h"
#include "IncrementalSweeper.h"
#include "JSCInlines.h"
#include "JSObject.h"
#include "JSString.h"
#include <wtf/MainThread.h>
#include <wtf/NoTailCalls.h>
#include <wtf/Threading.h>
#if USE(GLIB_EVENT_LOOP)
#include <glib.h>
#include <wtf/glib/RunLoopSourcePriority.h>
#endif
#include <mutex>
namespace JSC {
const Seconds JSRunLoopTimer::s_decade { 60 * 60 * 24 * 365 * 10 };
static inline JSRunLoopTimer::Manager::EpochTime epochTime(Seconds delay)
{
#if USE(CF)
return Seconds { CFAbsoluteTimeGetCurrent() + delay.value() };
#else
return MonotonicTime::now().secondsSinceEpoch() + delay;
#endif
}
#if USE(CF)
void JSRunLoopTimer::Manager::timerDidFireCallback(CFRunLoopTimerRef, void* contextPtr)
{
static_cast<JSRunLoopTimer::Manager*>(contextPtr)->timerDidFire();
}
void JSRunLoopTimer::Manager::PerVMData::setRunLoop(Manager* manager, CFRunLoopRef newRunLoop)
{
if (runLoop) {
CFRunLoopRemoveTimer(runLoop.get(), timer.get(), kCFRunLoopCommonModes);
CFRunLoopTimerInvalidate(timer.get());
runLoop.clear();
timer.clear();
}
if (newRunLoop) {
runLoop = newRunLoop;
memset(&context, 0, sizeof(CFRunLoopTimerContext));
RELEASE_ASSERT(manager);
context.info = manager;
timer = adoptCF(CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + s_decade.seconds(), CFAbsoluteTimeGetCurrent() + s_decade.seconds(), 0, 0, JSRunLoopTimer::Manager::timerDidFireCallback, &context));
CFRunLoopAddTimer(runLoop.get(), timer.get(), kCFRunLoopCommonModes);
EpochTime scheduleTime = epochTime(s_decade);
for (auto& pair : timers)
scheduleTime = std::min(pair.second, scheduleTime);
CFRunLoopTimerSetNextFireDate(timer.get(), scheduleTime.value());
}
}
#else
JSRunLoopTimer::Manager::PerVMData::PerVMData(Manager& manager)
: runLoop(&RunLoop::current())
, 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();
}
#endif
JSRunLoopTimer::Manager::PerVMData::~PerVMData()
{
#if USE(CF)
setRunLoop(nullptr, nullptr);
#endif
}
void JSRunLoopTimer::Manager::timerDidFire()
{
Vector<Ref<JSRunLoopTimer>> timersToFire;
{
auto locker = holdLock(m_lock);
#if USE(CF)
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
#else
RunLoop* currentRunLoop = &RunLoop::current();
#endif
EpochTime nowEpochTime = epochTime(0_s);
for (auto& entry : m_mapping) {
PerVMData& data = *entry.value;
#if USE(CF)
if (data.runLoop.get() != currentRunLoop)
continue;
#else
if (data.runLoop != currentRunLoop)
continue;
#endif
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));
}
#if USE(CF)
CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
#else
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
#endif
}
}
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);
#if USE(CF)
data->setRunLoop(this, vm.runLoop());
#endif
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()); // We don't allow calling this after the VM dies.
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 });
#if USE(CF)
CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
#else
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
#endif
}
void JSRunLoopTimer::Manager::cancelTimer(JSRunLoopTimer& timer)
{
auto locker = holdLock(m_lock);
auto iter = m_mapping.find(timer.m_apiLock);
if (iter == m_mapping.end()) {
// It's trivial to allow this to be called after the VM dies, so we allow for it.
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); // If we remove it from the entry below, we should not be the last thing pointing to it!
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);
}
#if USE(CF)
CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
#else
data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
#endif
}
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()); // We only allow this to be called with a live VM.
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;
}
#if USE(CF)
void JSRunLoopTimer::Manager::didChangeRunLoop(VM& vm, CFRunLoopRef newRunLoop)
{
auto locker = holdLock(m_lock);
auto iter = m_mapping.find({ vm.apiLock() });
RELEASE_ASSERT(iter != m_mapping.end());
PerVMData& data = *iter->value;
data.setRunLoop(this, newRunLoop);
}
#endif
void JSRunLoopTimer::timerDidFire()
{
NO_TAIL_CALLS();
{
auto locker = holdLock(m_lock);
if (!m_isScheduled) {
// We raced between this callback being called and cancel() being called.
// That's fine, we just don't do anything here.
return;
}
}
std::lock_guard<JSLock> lock(m_apiLock.get());
RefPtr<VM> vm = m_apiLock->vm();
if (!vm) {
// The VM has been destroyed, so we should just give up.
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);
}
} // namespace JSC