| /* |
| * Copyright (C) 2013, 2015 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 "Watchdog.h" |
| |
| #include "CallFrame.h" |
| #include <wtf/CurrentTime.h> |
| #include <wtf/MathExtras.h> |
| |
| namespace JSC { |
| |
| const std::chrono::microseconds Watchdog::noTimeLimit = std::chrono::microseconds::max(); |
| |
| static std::chrono::microseconds currentWallClockTime() |
| { |
| auto steadyTimeSinceEpoch = std::chrono::steady_clock::now().time_since_epoch(); |
| return std::chrono::duration_cast<std::chrono::microseconds>(steadyTimeSinceEpoch); |
| } |
| |
| Watchdog::Watchdog() |
| : m_timerDidFire(false) |
| , m_timeLimit(noTimeLimit) |
| , m_cpuDeadline(noTimeLimit) |
| , m_wallClockDeadline(noTimeLimit) |
| , m_callback(0) |
| , m_callbackData1(0) |
| , m_callbackData2(0) |
| , m_timerQueue(WorkQueue::create("jsc.watchdog.queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility)) |
| { |
| m_timerHandler = [this] { |
| { |
| LockHolder locker(m_lock); |
| this->m_timerDidFire = true; |
| } |
| this->deref(); |
| }; |
| } |
| |
| void Watchdog::setTimeLimit(std::chrono::microseconds limit, |
| ShouldTerminateCallback callback, void* data1, void* data2) |
| { |
| LockHolder locker(m_lock); |
| |
| m_timeLimit = limit; |
| m_callback = callback; |
| m_callbackData1 = data1; |
| m_callbackData2 = data2; |
| |
| if (m_hasEnteredVM && hasTimeLimit()) |
| startTimer(locker, m_timeLimit); |
| } |
| |
| JS_EXPORT_PRIVATE void Watchdog::terminateSoon() |
| { |
| LockHolder locker(m_lock); |
| |
| m_timeLimit = std::chrono::microseconds(0); |
| m_cpuDeadline = std::chrono::microseconds(0); |
| m_wallClockDeadline = std::chrono::microseconds(0); |
| m_timerDidFire = true; |
| } |
| |
| bool Watchdog::didFireSlow(ExecState* exec) |
| { |
| { |
| LockHolder locker(m_lock); |
| |
| ASSERT(m_timerDidFire); |
| m_timerDidFire = false; |
| |
| if (currentWallClockTime() < m_wallClockDeadline) |
| return false; // Just a stale timer firing. Nothing to do. |
| |
| // Set m_wallClockDeadline to noTimeLimit here so that we can reject all future |
| // spurious wakes. |
| m_wallClockDeadline = noTimeLimit; |
| |
| auto cpuTime = currentCPUTime(); |
| if (cpuTime < m_cpuDeadline) { |
| auto remainingCPUTime = m_cpuDeadline - cpuTime; |
| startTimer(locker, remainingCPUTime); |
| return false; |
| } |
| } |
| |
| // Note: we should not be holding the lock while calling the callbacks. The callbacks may |
| // call setTimeLimit() which will try to lock as well. |
| |
| // If m_callback is not set, then we terminate by default. |
| // Else, we let m_callback decide if we should terminate or not. |
| bool needsTermination = !m_callback |
| || m_callback(exec, m_callbackData1, m_callbackData2); |
| if (needsTermination) |
| return true; |
| |
| { |
| LockHolder locker(m_lock); |
| |
| // If we get here, then the callback above did not want to terminate execution. As a |
| // result, the callback may have done one of the following: |
| // 1. cleared the time limit (i.e. watchdog is disabled), |
| // 2. set a new time limit via Watchdog::setTimeLimit(), or |
| // 3. did nothing (i.e. allow another cycle of the current time limit). |
| // |
| // In the case of 1, we don't have to do anything. |
| // In the case of 2, Watchdog::setTimeLimit() would already have started the timer. |
| // In the case of 3, we need to re-start the timer here. |
| |
| ASSERT(m_hasEnteredVM); |
| bool callbackAlreadyStartedTimer = (m_cpuDeadline != noTimeLimit); |
| if (hasTimeLimit() && !callbackAlreadyStartedTimer) |
| startTimer(locker, m_timeLimit); |
| } |
| return false; |
| } |
| |
| bool Watchdog::hasTimeLimit() |
| { |
| return (m_timeLimit != noTimeLimit); |
| } |
| |
| void Watchdog::enteredVM() |
| { |
| m_hasEnteredVM = true; |
| if (hasTimeLimit()) { |
| LockHolder locker(m_lock); |
| startTimer(locker, m_timeLimit); |
| } |
| } |
| |
| void Watchdog::exitedVM() |
| { |
| ASSERT(m_hasEnteredVM); |
| LockHolder locker(m_lock); |
| stopTimer(locker); |
| m_hasEnteredVM = false; |
| } |
| |
| void Watchdog::startTimer(LockHolder&, std::chrono::microseconds timeLimit) |
| { |
| ASSERT(m_hasEnteredVM); |
| ASSERT(hasTimeLimit()); |
| ASSERT(timeLimit <= m_timeLimit); |
| |
| m_cpuDeadline = currentCPUTime() + timeLimit; |
| auto wallClockTime = currentWallClockTime(); |
| auto wallClockDeadline = wallClockTime + timeLimit; |
| |
| if ((wallClockTime < m_wallClockDeadline) |
| && (m_wallClockDeadline <= wallClockDeadline)) |
| return; // Wait for the current active timer to expire before starting a new one. |
| |
| // Else, the current active timer won't fire soon enough. So, start a new timer. |
| this->ref(); // m_timerHandler will deref to match later. |
| m_wallClockDeadline = wallClockDeadline; |
| |
| m_timerQueue->dispatchAfter(std::chrono::nanoseconds(timeLimit), m_timerHandler); |
| } |
| |
| void Watchdog::stopTimer(LockHolder&) |
| { |
| m_cpuDeadline = noTimeLimit; |
| } |
| |
| } // namespace JSC |