| /* |
| * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2009 Google 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 "Timer.h" |
| |
| #include "RuntimeApplicationChecks.h" |
| #include "SharedTimer.h" |
| #include "ThreadGlobalData.h" |
| #include "ThreadTimers.h" |
| #include <limits> |
| #include <math.h> |
| #include <wtf/MainThread.h> |
| #include <wtf/Vector.h> |
| |
| #if PLATFORM(COCOA) |
| #include <wtf/cocoa/RuntimeApplicationChecksCocoa.h> |
| #endif |
| |
| namespace WebCore { |
| |
| class TimerHeapReference; |
| |
| // Timers are stored in a heap data structure, used to implement a priority queue. |
| // This allows us to efficiently determine which timer needs to fire the soonest. |
| // Then we set a single shared system timer to fire at that time. |
| // |
| // When a timer's "next fire time" changes, we need to move it around in the priority queue. |
| #if ASSERT_ENABLED |
| static ThreadTimerHeap& threadGlobalTimerHeap() |
| { |
| return threadGlobalData().threadTimers().timerHeap(); |
| } |
| #endif |
| |
| inline ThreadTimerHeapItem::ThreadTimerHeapItem(TimerBase& timer, MonotonicTime time, unsigned insertionOrder) |
| : time(time) |
| , insertionOrder(insertionOrder) |
| , m_threadTimers(threadGlobalData().threadTimers()) |
| , m_timer(&timer) |
| { |
| ASSERT(m_timer); |
| } |
| |
| inline RefPtr<ThreadTimerHeapItem> ThreadTimerHeapItem::create(TimerBase& timer, MonotonicTime time, unsigned insertionOrder) |
| { |
| return adoptRef(*new ThreadTimerHeapItem { timer, time, insertionOrder }); |
| } |
| |
| // ---------------- |
| |
| class TimerHeapPointer { |
| public: |
| TimerHeapPointer(RefPtr<ThreadTimerHeapItem>* pointer) |
| : m_pointer(pointer) |
| { } |
| |
| TimerHeapReference operator*() const; |
| RefPtr<ThreadTimerHeapItem>& operator->() const { return *m_pointer; } |
| private: |
| RefPtr<ThreadTimerHeapItem>* m_pointer; |
| }; |
| |
| class TimerHeapReference { |
| public: |
| TimerHeapReference(RefPtr<ThreadTimerHeapItem>& reference) |
| : m_reference(reference) |
| { } |
| |
| TimerHeapReference(const TimerHeapReference& other) |
| : m_reference(other.m_reference) |
| { } |
| |
| operator RefPtr<ThreadTimerHeapItem>&() const { return m_reference; } |
| TimerHeapPointer operator&() const { return &m_reference; } |
| TimerHeapReference& operator=(TimerHeapReference&&); |
| TimerHeapReference& operator=(RefPtr<ThreadTimerHeapItem>&&); |
| |
| void swap(TimerHeapReference& other); |
| |
| void updateHeapIndex(); |
| |
| private: |
| RefPtr<ThreadTimerHeapItem>& m_reference; |
| |
| friend void swap(TimerHeapReference a, TimerHeapReference b); |
| }; |
| |
| inline TimerHeapReference TimerHeapPointer::operator*() const |
| { |
| return TimerHeapReference { *m_pointer }; |
| } |
| |
| inline TimerHeapReference& TimerHeapReference::operator=(TimerHeapReference&& other) |
| { |
| m_reference = WTFMove(other.m_reference); |
| updateHeapIndex(); |
| return *this; |
| } |
| |
| inline TimerHeapReference& TimerHeapReference::operator=(RefPtr<ThreadTimerHeapItem>&& item) |
| { |
| m_reference = WTFMove(item); |
| updateHeapIndex(); |
| return *this; |
| } |
| |
| inline void TimerHeapReference::swap(TimerHeapReference& other) |
| { |
| m_reference.swap(other.m_reference); |
| updateHeapIndex(); |
| other.updateHeapIndex(); |
| } |
| |
| inline void TimerHeapReference::updateHeapIndex() |
| { |
| auto& heap = m_reference->timerHeap(); |
| if (&m_reference >= heap.data() && &m_reference < heap.data() + heap.size()) |
| m_reference->setHeapIndex(&m_reference - heap.data()); |
| } |
| |
| inline void swap(TimerHeapReference a, TimerHeapReference b) |
| { |
| a.swap(b); |
| } |
| |
| // ---------------- |
| |
| // Class to represent iterators in the heap when calling the standard library heap algorithms. |
| // Uses a custom pointer and reference type that update indices for pointers in the heap. |
| class TimerHeapIterator { |
| public: |
| using iterator_category = std::random_access_iterator_tag; |
| using value_type = RefPtr<ThreadTimerHeapItem>; |
| using difference_type = ptrdiff_t; |
| using pointer = TimerHeapPointer; |
| using reference = TimerHeapReference; |
| |
| explicit TimerHeapIterator(RefPtr<ThreadTimerHeapItem>* pointer) : m_pointer(pointer) { checkConsistency(); } |
| |
| TimerHeapIterator& operator++() { checkConsistency(); ++m_pointer; checkConsistency(); return *this; } |
| TimerHeapIterator operator++(int) { checkConsistency(1); return TimerHeapIterator(m_pointer++); } |
| |
| TimerHeapIterator& operator--() { checkConsistency(); --m_pointer; checkConsistency(); return *this; } |
| TimerHeapIterator operator--(int) { checkConsistency(-1); return TimerHeapIterator(m_pointer--); } |
| |
| TimerHeapIterator& operator+=(ptrdiff_t i) { checkConsistency(); m_pointer += i; checkConsistency(); return *this; } |
| TimerHeapIterator& operator-=(ptrdiff_t i) { checkConsistency(); m_pointer -= i; checkConsistency(); return *this; } |
| |
| TimerHeapReference operator*() const { return TimerHeapReference(*m_pointer); } |
| TimerHeapReference operator[](ptrdiff_t i) const { return TimerHeapReference(m_pointer[i]); } |
| RefPtr<ThreadTimerHeapItem>& operator->() const { return *m_pointer; } |
| |
| private: |
| void checkConsistency(ptrdiff_t offset = 0) const |
| { |
| ASSERT(m_pointer >= threadGlobalTimerHeap().data()); |
| ASSERT(m_pointer <= threadGlobalTimerHeap().data() + threadGlobalTimerHeap().size()); |
| ASSERT_UNUSED(offset, m_pointer + offset >= threadGlobalTimerHeap().data()); |
| ASSERT_UNUSED(offset, m_pointer + offset <= threadGlobalTimerHeap().data() + threadGlobalTimerHeap().size()); |
| } |
| |
| friend bool operator==(TimerHeapIterator, TimerHeapIterator); |
| friend bool operator!=(TimerHeapIterator, TimerHeapIterator); |
| friend bool operator<(TimerHeapIterator, TimerHeapIterator); |
| friend bool operator>(TimerHeapIterator, TimerHeapIterator); |
| friend bool operator<=(TimerHeapIterator, TimerHeapIterator); |
| friend bool operator>=(TimerHeapIterator, TimerHeapIterator); |
| |
| friend TimerHeapIterator operator+(TimerHeapIterator, size_t); |
| friend TimerHeapIterator operator+(size_t, TimerHeapIterator); |
| |
| friend TimerHeapIterator operator-(TimerHeapIterator, size_t); |
| friend ptrdiff_t operator-(TimerHeapIterator, TimerHeapIterator); |
| |
| RefPtr<ThreadTimerHeapItem>* m_pointer; |
| }; |
| |
| inline bool operator==(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer == b.m_pointer; } |
| inline bool operator!=(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer != b.m_pointer; } |
| inline bool operator<(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer < b.m_pointer; } |
| inline bool operator>(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer > b.m_pointer; } |
| inline bool operator<=(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer <= b.m_pointer; } |
| inline bool operator>=(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer >= b.m_pointer; } |
| |
| inline TimerHeapIterator operator+(TimerHeapIterator a, size_t b) { return TimerHeapIterator(a.m_pointer + b); } |
| inline TimerHeapIterator operator+(size_t a, TimerHeapIterator b) { return TimerHeapIterator(a + b.m_pointer); } |
| |
| inline TimerHeapIterator operator-(TimerHeapIterator a, size_t b) { return TimerHeapIterator(a.m_pointer - b); } |
| inline ptrdiff_t operator-(TimerHeapIterator a, TimerHeapIterator b) { return a.m_pointer - b.m_pointer; } |
| |
| // ---------------- |
| |
| class TimerHeapLessThanFunction { |
| public: |
| static bool compare(const TimerBase& a, const RefPtr<ThreadTimerHeapItem>& b) |
| { |
| return compare(a.m_heapItem->time, a.m_heapItem->insertionOrder, b->time, b->insertionOrder); |
| } |
| |
| static bool compare(const RefPtr<ThreadTimerHeapItem>& a, const TimerBase& b) |
| { |
| return compare(a->time, a->insertionOrder, b.m_heapItem->time, b.m_heapItem->insertionOrder); |
| } |
| |
| bool operator()(const RefPtr<ThreadTimerHeapItem>& a, const RefPtr<ThreadTimerHeapItem>& b) const |
| { |
| return compare(a->time, a->insertionOrder, b->time, b->insertionOrder); |
| } |
| |
| private: |
| static bool compare(MonotonicTime aTime, unsigned aOrder, MonotonicTime bTime, unsigned bOrder) |
| { |
| // The comparisons below are "backwards" because the heap puts the largest |
| // element first and we want the lowest time to be the first one in the heap. |
| if (bTime != aTime) |
| return bTime < aTime; |
| // We need to look at the difference of the insertion orders instead of comparing the two |
| // outright in case of overflow. |
| unsigned difference = aOrder - bOrder; |
| return difference < std::numeric_limits<unsigned>::max() / 2; |
| } |
| }; |
| |
| // ---------------- |
| |
| static bool shouldSuppressThreadSafetyCheck() |
| { |
| #if PLATFORM(IOS_FAMILY) |
| return WebThreadIsEnabled() || !linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::TimerThreadSafetyChecks); |
| #elif PLATFORM(MAC) |
| return !isInWebProcess() && !linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::TimerThreadSafetyChecks); |
| #else |
| return false; |
| #endif |
| } |
| |
| TimerBase::TimerBase() |
| { |
| } |
| |
| TimerBase::~TimerBase() |
| { |
| ASSERT(canCurrentThreadAccessThreadLocalData(m_thread)); |
| RELEASE_ASSERT(canCurrentThreadAccessThreadLocalData(m_thread) || shouldSuppressThreadSafetyCheck()); |
| stop(); |
| ASSERT(!inHeap()); |
| if (m_heapItem) |
| m_heapItem->clearTimer(); |
| m_unalignedNextFireTime = MonotonicTime::nan(); |
| } |
| |
| void TimerBase::start(Seconds nextFireInterval, Seconds repeatInterval) |
| { |
| ASSERT(canCurrentThreadAccessThreadLocalData(m_thread)); |
| |
| m_repeatInterval = repeatInterval; |
| setNextFireTime(MonotonicTime::now() + nextFireInterval); |
| } |
| |
| void TimerBase::stop() |
| { |
| ASSERT(canCurrentThreadAccessThreadLocalData(m_thread)); |
| |
| m_repeatInterval = 0_s; |
| setNextFireTime(MonotonicTime { }); |
| |
| ASSERT(!static_cast<bool>(nextFireTime())); |
| ASSERT(m_repeatInterval == 0_s); |
| ASSERT(!inHeap()); |
| } |
| |
| Seconds TimerBase::nextFireInterval() const |
| { |
| ASSERT(isActive()); |
| ASSERT(m_heapItem); |
| MonotonicTime current = MonotonicTime::now(); |
| auto fireTime = nextFireTime(); |
| if (fireTime < current) |
| return 0_s; |
| return fireTime - current; |
| } |
| |
| inline void TimerBase::checkHeapIndex() const |
| { |
| #if ASSERT_ENABLED |
| ASSERT(m_heapItem); |
| auto& heap = m_heapItem->timerHeap(); |
| ASSERT(&heap == &threadGlobalTimerHeap()); |
| ASSERT(!heap.isEmpty()); |
| ASSERT(m_heapItem->isInHeap()); |
| ASSERT(m_heapItem->heapIndex() < m_heapItem->timerHeap().size()); |
| ASSERT(heap[m_heapItem->heapIndex()] == m_heapItem); |
| for (unsigned i = 0, size = heap.size(); i < size; i++) |
| ASSERT(heap[i]->heapIndex() == i); |
| #endif |
| } |
| |
| inline void TimerBase::checkConsistency() const |
| { |
| // Timers should be in the heap if and only if they have a non-zero next fire time. |
| ASSERT(inHeap() == static_cast<bool>(nextFireTime())); |
| if (inHeap()) |
| checkHeapIndex(); |
| } |
| |
| void TimerBase::heapDecreaseKey() |
| { |
| ASSERT(static_cast<bool>(nextFireTime())); |
| ASSERT(m_heapItem); |
| checkHeapIndex(); |
| auto* heapData = m_heapItem->timerHeap().data(); |
| std::push_heap(TimerHeapIterator(heapData), TimerHeapIterator(heapData + m_heapItem->heapIndex() + 1), TimerHeapLessThanFunction()); |
| checkHeapIndex(); |
| } |
| |
| inline void TimerBase::heapDelete() |
| { |
| ASSERT(!static_cast<bool>(nextFireTime())); |
| heapPop(); |
| m_heapItem->timerHeap().removeLast(); |
| m_heapItem->setNotInHeap(); |
| } |
| |
| void TimerBase::heapDeleteMin() |
| { |
| ASSERT(!static_cast<bool>(nextFireTime())); |
| heapPopMin(); |
| m_heapItem->timerHeap().removeLast(); |
| m_heapItem->setNotInHeap(); |
| } |
| |
| inline void TimerBase::heapIncreaseKey() |
| { |
| ASSERT(static_cast<bool>(nextFireTime())); |
| heapPop(); |
| heapDecreaseKey(); |
| } |
| |
| inline void TimerBase::heapInsert() |
| { |
| ASSERT(!inHeap()); |
| ASSERT(m_heapItem); |
| auto& heap = m_heapItem->timerHeap(); |
| heap.append(m_heapItem.copyRef()); |
| m_heapItem->setHeapIndex(heap.size() - 1); |
| heapDecreaseKey(); |
| } |
| |
| inline void TimerBase::heapPop() |
| { |
| ASSERT(m_heapItem); |
| // Temporarily force this timer to have the minimum key so we can pop it. |
| MonotonicTime fireTime = m_heapItem->time; |
| m_heapItem->time = -MonotonicTime::infinity(); |
| heapDecreaseKey(); |
| heapPopMin(); |
| m_heapItem->time = fireTime; |
| } |
| |
| void TimerBase::heapPopMin() |
| { |
| ASSERT(m_heapItem == m_heapItem->timerHeap().first()); |
| checkHeapIndex(); |
| auto& heap = m_heapItem->timerHeap(); |
| auto* heapData = heap.data(); |
| std::pop_heap(TimerHeapIterator(heapData), TimerHeapIterator(heapData + heap.size()), TimerHeapLessThanFunction()); |
| checkHeapIndex(); |
| ASSERT(m_heapItem == m_heapItem->timerHeap().last()); |
| } |
| |
| void TimerBase::heapDeleteNullMin(ThreadTimerHeap& heap) |
| { |
| RELEASE_ASSERT(!heap.first()->hasTimer()); |
| heap.first()->time = -MonotonicTime::infinity(); |
| auto* heapData = heap.data(); |
| std::pop_heap(TimerHeapIterator(heapData), TimerHeapIterator(heapData + heap.size()), TimerHeapLessThanFunction()); |
| heap.removeLast(); |
| } |
| |
| static inline bool parentHeapPropertyHolds(const TimerBase* current, const ThreadTimerHeap& heap, unsigned currentIndex) |
| { |
| if (!currentIndex) |
| return true; |
| unsigned parentIndex = (currentIndex - 1) / 2; |
| return TimerHeapLessThanFunction::compare(*current, heap[parentIndex]); |
| } |
| |
| static inline bool childHeapPropertyHolds(const TimerBase* current, const ThreadTimerHeap& heap, unsigned childIndex) |
| { |
| if (childIndex >= heap.size()) |
| return true; |
| return TimerHeapLessThanFunction::compare(heap[childIndex], *current); |
| } |
| |
| bool TimerBase::hasValidHeapPosition() const |
| { |
| ASSERT(nextFireTime()); |
| ASSERT(m_heapItem); |
| if (!inHeap()) |
| return false; |
| // Check if the heap property still holds with the new fire time. If it does we don't need to do anything. |
| // This assumes that the STL heap is a standard binary heap. In an unlikely event it is not, the assertions |
| // in updateHeapIfNeeded() will get hit. |
| const auto& heap = m_heapItem->timerHeap(); |
| unsigned heapIndex = m_heapItem->heapIndex(); |
| if (!parentHeapPropertyHolds(this, heap, heapIndex)) |
| return false; |
| unsigned childIndex1 = 2 * heapIndex + 1; |
| unsigned childIndex2 = childIndex1 + 1; |
| return childHeapPropertyHolds(this, heap, childIndex1) && childHeapPropertyHolds(this, heap, childIndex2); |
| } |
| |
| void TimerBase::updateHeapIfNeeded(MonotonicTime oldTime) |
| { |
| auto fireTime = nextFireTime(); |
| if (fireTime && hasValidHeapPosition()) |
| return; |
| |
| #if ASSERT_ENABLED |
| std::optional<unsigned> oldHeapIndex; |
| if (m_heapItem->isInHeap()) |
| oldHeapIndex = m_heapItem->heapIndex(); |
| #endif |
| |
| if (!oldTime) |
| heapInsert(); |
| else if (!fireTime) |
| heapDelete(); |
| else if (fireTime < oldTime) |
| heapDecreaseKey(); |
| else |
| heapIncreaseKey(); |
| |
| #if ASSERT_ENABLED |
| std::optional<unsigned> newHeapIndex; |
| if (m_heapItem->isInHeap()) |
| newHeapIndex = m_heapItem->heapIndex(); |
| ASSERT(newHeapIndex != oldHeapIndex); |
| #endif |
| |
| ASSERT(!inHeap() || hasValidHeapPosition()); |
| } |
| |
| void TimerBase::setNextFireTime(MonotonicTime newTime) |
| { |
| ASSERT(canCurrentThreadAccessThreadLocalData(m_thread)); |
| RELEASE_ASSERT(canCurrentThreadAccessThreadLocalData(m_thread) || shouldSuppressThreadSafetyCheck()); |
| bool timerHasBeenDeleted = std::isnan(m_unalignedNextFireTime); |
| RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!timerHasBeenDeleted); |
| |
| if (m_unalignedNextFireTime != newTime) { |
| RELEASE_ASSERT(!std::isnan(newTime)); |
| m_unalignedNextFireTime = newTime; |
| } |
| |
| // Keep heap valid while changing the next-fire time. |
| MonotonicTime oldTime = nextFireTime(); |
| // Don't realign zero-delay timers. |
| if (newTime) { |
| if (auto newAlignedTime = alignedFireTime(newTime)) |
| newTime = newAlignedTime.value(); |
| } |
| |
| if (oldTime != newTime) { |
| auto newOrder = threadGlobalData().threadTimers().nextHeapInsertionCount(); |
| |
| if (!m_heapItem) |
| m_heapItem = ThreadTimerHeapItem::create(*this, newTime, 0); |
| m_heapItem->time = newTime; |
| m_heapItem->insertionOrder = newOrder; |
| |
| bool wasFirstTimerInHeap = m_heapItem->isFirstInHeap(); |
| |
| updateHeapIfNeeded(oldTime); |
| |
| bool isFirstTimerInHeap = m_heapItem->isFirstInHeap(); |
| |
| if (wasFirstTimerInHeap || isFirstTimerInHeap) |
| threadGlobalData().threadTimers().updateSharedTimer(); |
| } |
| |
| checkConsistency(); |
| } |
| |
| void TimerBase::fireTimersInNestedEventLoop() |
| { |
| // Redirect to ThreadTimers. |
| threadGlobalData().threadTimers().fireTimersInNestedEventLoop(); |
| } |
| |
| void TimerBase::didChangeAlignmentInterval() |
| { |
| setNextFireTime(m_unalignedNextFireTime); |
| } |
| |
| Seconds TimerBase::nextUnalignedFireInterval() const |
| { |
| ASSERT(isActive()); |
| auto result = std::max(m_unalignedNextFireTime - MonotonicTime::now(), 0_s); |
| RELEASE_ASSERT(std::isfinite(result)); |
| return result; |
| } |
| |
| } // namespace WebCore |
| |