| /* |
| * Copyright (C) 2013-2017 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 "VM.h" |
| #include <wtf/CPUTime.h> |
| |
| namespace JSC { |
| |
| Watchdog::Watchdog(VM* vm) |
| : m_vm(vm) |
| , m_timeLimit(noTimeLimit) |
| , m_cpuDeadline(noTimeLimit) |
| , m_deadline(MonotonicTime::infinity()) |
| , m_callback(nullptr) |
| , m_callbackData1(nullptr) |
| , m_callbackData2(nullptr) |
| , m_timerQueue(WorkQueue::create("jsc.watchdog.queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility)) |
| { |
| } |
| |
| void Watchdog::setTimeLimit(Seconds limit, |
| ShouldTerminateCallback callback, void* data1, void* data2) |
| { |
| ASSERT(m_vm->currentThreadIsHoldingAPILock()); |
| |
| m_timeLimit = limit; |
| m_callback = callback; |
| m_callbackData1 = data1; |
| m_callbackData2 = data2; |
| |
| if (m_hasEnteredVM && hasTimeLimit()) |
| startTimer(m_timeLimit); |
| } |
| |
| bool Watchdog::shouldTerminate(JSGlobalObject* globalObject) |
| { |
| ASSERT(m_vm->currentThreadIsHoldingAPILock()); |
| if (MonotonicTime::now() < m_deadline) |
| return false; // Just a stale timer firing. Nothing to do. |
| |
| // Set m_deadline to MonotonicTime::infinity() here so that we can reject all future |
| // spurious wakes. |
| m_deadline = MonotonicTime::infinity(); |
| |
| auto cpuTime = CPUTime::forCurrentThread(); |
| if (cpuTime < m_cpuDeadline) { |
| auto remainingCPUTime = m_cpuDeadline - cpuTime; |
| startTimer(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(globalObject, m_callbackData1, m_callbackData2); |
| if (needsTermination) |
| return true; |
| |
| // 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(m_timeLimit); |
| |
| return false; |
| } |
| |
| bool Watchdog::hasTimeLimit() |
| { |
| return (m_timeLimit != noTimeLimit); |
| } |
| |
| void Watchdog::enteredVM() |
| { |
| m_hasEnteredVM = true; |
| if (hasTimeLimit()) |
| startTimer(m_timeLimit); |
| } |
| |
| void Watchdog::exitedVM() |
| { |
| ASSERT(m_hasEnteredVM); |
| stopTimer(); |
| m_hasEnteredVM = false; |
| } |
| |
| void Watchdog::startTimer(Seconds timeLimit) |
| { |
| ASSERT(m_hasEnteredVM); |
| ASSERT(m_vm->currentThreadIsHoldingAPILock()); |
| ASSERT(hasTimeLimit()); |
| ASSERT(timeLimit <= m_timeLimit); |
| |
| m_cpuDeadline = CPUTime::forCurrentThread() + timeLimit; |
| auto now = MonotonicTime::now(); |
| auto deadline = now + timeLimit; |
| |
| if ((now < m_deadline) && (m_deadline <= deadline)) |
| 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. |
| m_deadline = deadline; |
| |
| // We need to ensure that the Watchdog outlives the timer. |
| // For the same reason, the timer may also outlive the VM that the Watchdog operates on. |
| // So, we always need to null check m_vm before using it. The VM will notify the Watchdog |
| // via willDestroyVM() before it goes away. |
| RefPtr<Watchdog> protectedThis = this; |
| m_timerQueue->dispatchAfter(timeLimit, [this, protectedThis] { |
| LockHolder locker(m_lock); |
| if (m_vm) |
| m_vm->notifyNeedWatchdogCheck(); |
| }); |
| } |
| |
| void Watchdog::stopTimer() |
| { |
| ASSERT(m_hasEnteredVM); |
| ASSERT(m_vm->currentThreadIsHoldingAPILock()); |
| m_cpuDeadline = noTimeLimit; |
| } |
| |
| void Watchdog::willDestroyVM(VM* vm) |
| { |
| LockHolder locker(m_lock); |
| ASSERT_UNUSED(vm, m_vm == vm); |
| m_vm = nullptr; |
| } |
| |
| } // namespace JSC |