/* * 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 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 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