| /* | 
 |  * 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 "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() | 
 | { | 
 |     // Because RunLoop::Timer is not reference counted, we need to deallocate it | 
 |     // on the same thread on which it fires; otherwise, we might deallocate it | 
 |     // while it's firing. | 
 |     runLoop->dispatch([timer = WTFMove(timer)] { | 
 |     }); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::Manager::timerDidFire() | 
 | { | 
 |     Vector<Ref<JSRunLoopTimer>> timersToFire; | 
 |  | 
 |     { | 
 |         Locker locker { 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()); | 
 |  | 
 |     Locker locker { m_lock }; | 
 |     auto addResult = m_mapping.add({ vm.apiLock() }, WTFMove(data)); | 
 |     RELEASE_ASSERT(addResult.isNewEntry); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::Manager::unregisterVM(VM& vm) | 
 | { | 
 |     Locker locker { 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); | 
 |  | 
 |     Locker locker { 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 }); | 
 |  | 
 |     data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch())); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::Manager::cancelTimer(JSRunLoopTimer& timer) | 
 | { | 
 |     Locker locker { 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); | 
 |     } | 
 |  | 
 |     data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch())); | 
 | } | 
 |  | 
 | std::optional<Seconds> JSRunLoopTimer::Manager::timeUntilFire(JSRunLoopTimer& timer) | 
 | { | 
 |     Locker locker { 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 std::nullopt; | 
 | } | 
 |  | 
 | void JSRunLoopTimer::timerDidFire() | 
 | { | 
 |     NO_TAIL_CALLS(); | 
 |  | 
 |     { | 
 |         Locker locker { 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; | 
 |         } | 
 |     } | 
 |  | 
 |     Locker locker { 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() | 
 | { | 
 | } | 
 |  | 
 | std::optional<Seconds> JSRunLoopTimer::timeUntilFire() | 
 | { | 
 |     return Manager::shared().timeUntilFire(*this); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::setTimeUntilFire(Seconds intervalInSeconds) | 
 | { | 
 |     { | 
 |         Locker locker { m_lock }; | 
 |         m_isScheduled = true; | 
 |         Manager::shared().scheduleTimer(*this, intervalInSeconds); | 
 |     } | 
 |  | 
 |     Locker locker { m_timerCallbacksLock }; | 
 |     for (auto& task : m_timerSetCallbacks) | 
 |         task->run(); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::cancelTimer() | 
 | { | 
 |     Locker locker { m_lock }; | 
 |     m_isScheduled = false; | 
 |     Manager::shared().cancelTimer(*this); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::addTimerSetNotification(TimerNotificationCallback callback) | 
 | { | 
 |     Locker locker { m_timerCallbacksLock }; | 
 |     m_timerSetCallbacks.add(callback); | 
 | } | 
 |  | 
 | void JSRunLoopTimer::removeTimerSetNotification(TimerNotificationCallback callback) | 
 | { | 
 |     Locker locker { m_timerCallbacksLock }; | 
 |     m_timerSetCallbacks.remove(callback); | 
 | } | 
 |  | 
 | } // namespace JSC |