diff options
author | Konstantin Tokarev <annulen@yandex.ru> | 2016-08-25 19:20:41 +0300 |
---|---|---|
committer | Konstantin Tokarev <annulen@yandex.ru> | 2017-02-02 12:30:55 +0000 |
commit | 6882a04fb36642862b11efe514251d32070c3d65 (patch) | |
tree | b7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/runtime/Watchdog.cpp | |
parent | ab6df191029eeeb0b0f16f127d553265659f739e (diff) | |
download | qtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz |
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f
Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/JavaScriptCore/runtime/Watchdog.cpp')
-rw-r--r-- | Source/JavaScriptCore/runtime/Watchdog.cpp | 223 |
1 files changed, 106 insertions, 117 deletions
diff --git a/Source/JavaScriptCore/runtime/Watchdog.cpp b/Source/JavaScriptCore/runtime/Watchdog.cpp index 573260b16..e463f4dc9 100644 --- a/Source/JavaScriptCore/runtime/Watchdog.cpp +++ b/Source/JavaScriptCore/runtime/Watchdog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * 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 @@ -32,168 +32,157 @@ namespace JSC { -#define NO_LIMIT std::numeric_limits<double>::infinity() +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_didFire(false) - , m_limit(NO_LIMIT) - , m_startTime(0) - , m_elapsedTime(0) - , m_reentryCount(0) - , m_isStopped(true) + , 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)) { - initTimer(); -} - -Watchdog::~Watchdog() -{ - ASSERT(!isArmed()); - stopCountdown(); - destroyTimer(); + m_timerHandler = [this] { + { + LockHolder locker(m_lock); + this->m_timerDidFire = true; + } + this->deref(); + }; } -void Watchdog::setTimeLimit(VM& vm, double limit, +void Watchdog::setTimeLimit(std::chrono::microseconds limit, ShouldTerminateCallback callback, void* data1, void* data2) { - bool wasEnabled = isEnabled(); - - if (!m_isStopped) - stopCountdown(); + LockHolder locker(m_lock); - m_didFire = false; // Reset the watchdog. - - m_limit = limit; + m_timeLimit = limit; m_callback = callback; m_callbackData1 = data1; m_callbackData2 = data2; - // If this is the first time that timeout is being enabled, then any - // previously JIT compiled code will not have the needed polling checks. - // Hence, we need to flush all the pre-existing compiled code. - // - // However, if the timeout is already enabled, and we're just changing the - // timeout value, then any existing JITted code will have the appropriate - // polling checks. Hence, there is no need to re-do this flushing. - if (!wasEnabled) { - // And if we've previously compiled any functions, we need to revert - // them because they don't have the needed polling checks yet. - vm.releaseExecutableMemory(); - } + if (m_hasEnteredVM && hasTimeLimit()) + startTimer(locker, m_timeLimit); +} + +JS_EXPORT_PRIVATE void Watchdog::terminateSoon() +{ + LockHolder locker(m_lock); - startCountdownIfNeeded(); + m_timeLimit = std::chrono::microseconds(0); + m_cpuDeadline = std::chrono::microseconds(0); + m_wallClockDeadline = std::chrono::microseconds(0); + m_timerDidFire = true; } -bool Watchdog::didFire(ExecState* exec) +bool Watchdog::shouldTerminateSlow(ExecState* exec) { - if (m_didFire) - return true; + { + LockHolder locker(m_lock); - if (!m_timerDidFire) - return false; - m_timerDidFire = false; - stopCountdown(); - - double currentTime = currentCPUTime(); - double deltaTime = currentTime - m_startTime; - double totalElapsedTime = m_elapsedTime + deltaTime; - if (totalElapsedTime > m_limit) { - // Case 1: the allowed CPU time has elapsed. - - // 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) { - m_didFire = true; - return true; - } + ASSERT(m_timerDidFire); + m_timerDidFire = false; - // The m_callback may have set a new limit. So, we may need to restart - // the countdown. - startCountdownIfNeeded(); + if (currentWallClockTime() < m_wallClockDeadline) + return false; // Just a stale timer firing. Nothing to do. - } else { - // Case 2: the allowed CPU time has NOT elapsed. + // Set m_wallClockDeadline to noTimeLimit here so that we can reject all future + // spurious wakes. + m_wallClockDeadline = noTimeLimit; - // Tell the timer to alarm us again when it thinks we've reached the - // end of the allowed time. - double remainingTime = m_limit - totalElapsedTime; - m_elapsedTime = totalElapsedTime; - m_startTime = currentTime; - startCountdown(remainingTime); + auto cpuTime = currentCPUTime(); + if (cpuTime < m_cpuDeadline) { + auto remainingCPUTime = m_cpuDeadline - cpuTime; + startTimer(locker, remainingCPUTime); + return false; + } } - 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. -bool Watchdog::isEnabled() -{ - return (m_limit != NO_LIMIT); + // 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; } -void Watchdog::fire() +bool Watchdog::hasTimeLimit() { - m_didFire = true; + return (m_timeLimit != noTimeLimit); } -void Watchdog::arm() +void Watchdog::enteredVM() { - m_reentryCount++; - if (m_reentryCount == 1) - startCountdownIfNeeded(); + m_hasEnteredVM = true; + if (hasTimeLimit()) { + LockHolder locker(m_lock); + startTimer(locker, m_timeLimit); + } } -void Watchdog::disarm() +void Watchdog::exitedVM() { - ASSERT(m_reentryCount > 0); - if (m_reentryCount == 1) - stopCountdown(); - m_reentryCount--; + ASSERT(m_hasEnteredVM); + LockHolder locker(m_lock); + stopTimer(locker); + m_hasEnteredVM = false; } -void Watchdog::startCountdownIfNeeded() +void Watchdog::startTimer(LockHolder&, std::chrono::microseconds timeLimit) { - if (!m_isStopped) - return; // Already started. + ASSERT(m_hasEnteredVM); + ASSERT(hasTimeLimit()); + ASSERT(timeLimit <= m_timeLimit); - if (!isArmed()) - return; // Not executing JS script. No need to start. + m_cpuDeadline = currentCPUTime() + timeLimit; + auto wallClockTime = currentWallClockTime(); + auto wallClockDeadline = wallClockTime + timeLimit; - if (isEnabled()) { - m_elapsedTime = 0; - m_startTime = currentCPUTime(); - startCountdown(m_limit); - } -} - -void Watchdog::startCountdown(double limit) -{ - ASSERT(m_isStopped); - m_isStopped = false; - startTimer(limit); -} + if ((wallClockTime < m_wallClockDeadline) + && (m_wallClockDeadline <= wallClockDeadline)) + return; // Wait for the current active timer to expire before starting a new one. -void Watchdog::stopCountdown() -{ - if (m_isStopped) - return; - stopTimer(); - m_isStopped = true; -} + // 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; -Watchdog::Scope::Scope(Watchdog& watchdog) - : m_watchdog(watchdog) -{ - m_watchdog.arm(); + m_timerQueue->dispatchAfter(std::chrono::nanoseconds(timeLimit), m_timerHandler); } -Watchdog::Scope::~Scope() +void Watchdog::stopTimer(LockHolder&) { - m_watchdog.disarm(); + m_cpuDeadline = noTimeLimit; } } // namespace JSC |