blob: 7ac74579c718c0d6ecc45d535b86d690bb846334 [file] [edit]
/*
* Copyright (C) 2016 Konstantin Tokavev <annulen@yandex.ru>
* Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>
*
* 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 <wtf/RunLoop.h>
#include <wtf/DataLog.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/ProcessID.h>
#include <wtf/TZoneMallocInlines.h>
namespace WTF {
static constexpr bool report = false;
class RunLoop::TimerBase::ScheduledTask final : public ThreadSafeRefCounted<ScheduledTask>, public RedBlackTree<ScheduledTask, MonotonicTime>::ThreadSafeNode {
WTF_MAKE_TZONE_ALLOCATED(ScheduledTask);
WTF_MAKE_NONCOPYABLE(ScheduledTask);
WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScheduledTask);
public:
static Ref<ScheduledTask> create(RunLoop::TimerBase& timer)
{
return adoptRef(*new ScheduledTask(timer));
}
void fired()
{
if (!isActive())
return;
if (!m_isRepeating)
deactivate();
if (isActive())
updateReadyTime();
m_timer.fired();
}
MonotonicTime scheduledTimePoint() const
{
return m_scheduledTimePoint;
}
void updateReadyTime()
{
ASSERT(!isScheduled());
m_scheduledTimePoint = MonotonicTime::now();
if (!m_fireInterval)
return;
m_scheduledTimePoint += m_fireInterval;
}
MonotonicTime key() const
{
return scheduledTimePoint();
}
bool isScheduled() const
{
return m_isScheduled;
}
void setScheduled(bool flag)
{
m_isScheduled = flag;
}
bool isActive() const
{
return m_isActive.load();
}
void activate(Seconds interval, bool repeating)
{
m_fireInterval = interval;
m_isRepeating = repeating;
updateReadyTime();
m_isActive.store(true);
}
void deactivate()
{
m_isActive.store(false);
}
private:
ScheduledTask(RunLoop::TimerBase& timer)
: m_timer(timer)
{
}
RunLoop::TimerBase& m_timer;
MonotonicTime m_scheduledTimePoint;
Seconds m_fireInterval;
std::atomic<bool> m_isActive { };
bool m_isRepeating { };
bool m_isScheduled { };
};
WTF_MAKE_TZONE_ALLOCATED_IMPL(RunLoop::TimerBase::ScheduledTask);
RunLoop::RunLoop() = default;
RunLoop::~RunLoop()
{
Locker locker { m_loopLock };
m_shutdown = true;
m_readyToRun.notifyOne();
// Here is running main loops. Wait until all the main loops are destroyed.
if (!m_mainLoops.isEmpty())
m_stopCondition.wait(m_loopLock);
}
inline bool RunLoop::populateTasks(RunMode runMode, Status& statusOfThisLoop, Deque<Ref<TimerBase::ScheduledTask>>& firedTimers)
{
Locker locker { m_loopLock };
if (runMode == RunMode::Drain) {
MonotonicTime sleepUntil = MonotonicTime::infinity();
if (!m_schedules.isEmpty())
sleepUntil = m_schedules.first()->scheduledTimePoint();
m_readyToRun.waitUntil(m_loopLock, sleepUntil, [&] {
return m_shutdown || m_pendingTasks || statusOfThisLoop == Status::Stopping;
});
}
if (statusOfThisLoop == Status::Stopping || m_shutdown) {
m_mainLoops.removeLast();
if (m_mainLoops.isEmpty())
m_stopCondition.notifyOne();
return false;
}
m_pendingTasks = false;
if (runMode == RunMode::Iterate)
statusOfThisLoop = Status::Stopping;
// Check expired timers.
MonotonicTime now = MonotonicTime::now();
while (!m_schedules.isEmpty()) {
auto task = m_schedules.first();
if (task->scheduledTimePoint() > now)
break;
unscheduleWithLock(*task);
firedTimers.append(Ref(*task));
}
return true;
}
void RunLoop::runImpl(RunMode runMode)
{
ASSERT(this == &RunLoop::currentSingleton());
if constexpr (report) {
static LazyNeverDestroyed<Timer> reporter;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
reporter.construct(*this, "RunLoop::runImpl::Reporter"_s, [this] {
unsigned count = 0;
unsigned active = 0;
for (auto task = m_schedules.first(); task; task = task->successor()) {
count++;
if (task->isActive())
active++;
}
dataLogF("[RunLoop][pid %d main %s] m_schedules size %u active %u\n",
getCurrentProcessID(),
(isMain() ? "true" : "false"),
count,
active);
});
reporter->startRepeating(30_s);
dataLogF("[RunLoop][pid %d main %s] Reporter installed\n",
getCurrentProcessID(),
(isMain() ? "true" : "false"));
});
}
Status statusOfThisLoop = Status::Clear;
{
Locker locker { m_loopLock };
m_mainLoops.append(&statusOfThisLoop);
}
Deque<Ref<TimerBase::ScheduledTask>> firedTimers;
while (true) {
if (!populateTasks(runMode, statusOfThisLoop, firedTimers))
return;
// Dispatch scheduled timers.
while (!firedTimers.isEmpty()) {
auto task = firedTimers.takeFirst();
task->fired();
Locker locker { m_loopLock };
// It is possible the task is already scheduled while executing fired().
if (task->isActive() && !task->isScheduled()) {
// Reschedule because the timer requires repeating.
// Since we will query the timers' time points before sleeping,
// we do not call wakeUp() here.
scheduleWithLock(task.get());
}
}
performWork();
}
}
void RunLoop::run()
{
RunLoop::currentSingleton().runImpl(RunMode::Drain);
}
void RunLoop::setWakeUpCallback(WTF::Function<void()>&& function)
{
RunLoop::currentSingleton().m_wakeUpCallback = WTF::move(function);
}
// RunLoop operations are thread-safe. These operations can be called from outside of the RunLoop's thread.
// For example, WorkQueue::{dispatch, dispatchAfter} call the operations of the WorkQueue thread's RunLoop
// from the caller's thread.
void RunLoop::stop()
{
Locker locker { m_loopLock };
if (m_mainLoops.isEmpty())
return;
Status* status = m_mainLoops.last();
if (*status != Status::Stopping) {
*status = Status::Stopping;
m_readyToRun.notifyOne();
}
}
void RunLoop::wakeUpWithLock()
{
m_pendingTasks = true;
m_readyToRun.notifyOne();
if (m_wakeUpCallback)
m_wakeUpCallback();
}
void RunLoop::wakeUp()
{
Locker locker { m_loopLock };
wakeUpWithLock();
}
RunLoop::CycleResult RunLoop::cycle(RunLoopMode)
{
RunLoop::currentSingleton().runImpl(RunMode::Iterate);
return CycleResult::Continue;
}
void RunLoop::scheduleWithLock(TimerBase::ScheduledTask& task)
{
if (!task.isScheduled()) {
m_schedules.insert(&task);
task.setScheduled(true);
}
}
void RunLoop::unscheduleWithLock(TimerBase::ScheduledTask& task)
{
if (task.isScheduled()) {
m_schedules.remove(&task);
task.setScheduled(false);
}
}
// Since RunLoop does not own the registered TimerBase,
// TimerBase and its owner should manage these lifetime.
RunLoop::TimerBase::TimerBase(Ref<RunLoop>&& runLoop, ASCIILiteral description)
: m_runLoop(WTF::move(runLoop))
, m_description(description)
, m_scheduledTask(ScheduledTask::create(*this))
{
}
RunLoop::TimerBase::~TimerBase()
{
// An active timer must be stopped/destroyed on its run loop's thread: fired() runs the timer's
// callback on that thread without holding m_loopLock, and the TimerBase is not ref-counted, so
// tearing it down from another thread races with the in-flight callback and risks a use-after-free.
// (Starting a timer cross-thread is safe and supported -- that is how dispatch()/dispatchAfter()
// schedule work onto another run loop.)
if (m_scheduledTask->isActive())
releaseAssertIsCurrent(m_runLoop);
Locker locker { m_runLoop->m_loopLock };
stopWithLock();
}
void RunLoop::TimerBase::start(Seconds interval, bool repeating)
{
Locker locker { m_runLoop->m_loopLock };
stopWithLock();
m_scheduledTask->activate(interval, repeating);
m_runLoop->scheduleWithLock(m_scheduledTask.get());
m_runLoop->wakeUpWithLock();
}
void RunLoop::TimerBase::stopWithLock()
{
m_runLoop->unscheduleWithLock(m_scheduledTask.get());
m_scheduledTask->deactivate();
}
void RunLoop::TimerBase::stop()
{
Locker locker { m_runLoop->m_loopLock };
if (m_scheduledTask->isActive())
releaseAssertIsCurrent(m_runLoop);
stopWithLock();
}
bool RunLoop::TimerBase::isActive() const
{
Locker locker { m_runLoop->m_loopLock };
return isActiveWithLock();
}
bool RunLoop::TimerBase::isActiveWithLock() const
{
return m_scheduledTask->isActive();
}
Seconds RunLoop::TimerBase::secondsUntilFire() const
{
Locker locker { m_runLoop->m_loopLock };
if (isActiveWithLock())
return std::max<Seconds>(m_scheduledTask->scheduledTimePoint() - MonotonicTime::now(), 0_s);
return 0_s;
}
} // namespace WTF