mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-04-17 14:30:01 +00:00
357 lines
10 KiB
C++
357 lines
10 KiB
C++
/*
|
|
* Copyright (C) 2012-2018 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(std::make_unique<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 = std::make_unique<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
|