1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "Watchdog.h" 28 29#include "CallFrame.h" 30#include <wtf/CurrentTime.h> 31#include <wtf/MathExtras.h> 32 33namespace JSC { 34 35#define NO_LIMIT std::numeric_limits<double>::infinity() 36 37Watchdog::Watchdog() 38 : m_timerDidFire(false) 39 , m_didFire(false) 40 , m_limit(NO_LIMIT) 41 , m_startTime(0) 42 , m_elapsedTime(0) 43 , m_reentryCount(0) 44 , m_isStopped(true) 45 , m_callback(0) 46 , m_callbackData1(0) 47 , m_callbackData2(0) 48{ 49 initTimer(); 50} 51 52Watchdog::~Watchdog() 53{ 54 ASSERT(!isArmed()); 55 stopCountdown(); 56 destroyTimer(); 57} 58 59void Watchdog::setTimeLimit(VM& vm, double limit, 60 ShouldTerminateCallback callback, void* data1, void* data2) 61{ 62 bool wasEnabled = isEnabled(); 63 64 if (!m_isStopped) 65 stopCountdown(); 66 67 m_didFire = false; // Reset the watchdog. 68 69 m_limit = limit; 70 m_callback = callback; 71 m_callbackData1 = data1; 72 m_callbackData2 = data2; 73 74 // If this is the first time that timeout is being enabled, then any 75 // previously JIT compiled code will not have the needed polling checks. 76 // Hence, we need to flush all the pre-existing compiled code. 77 // 78 // However, if the timeout is already enabled, and we're just changing the 79 // timeout value, then any existing JITted code will have the appropriate 80 // polling checks. Hence, there is no need to re-do this flushing. 81 if (!wasEnabled) { 82 // And if we've previously compiled any functions, we need to revert 83 // them because they don't have the needed polling checks yet. 84 vm.releaseExecutableMemory(); 85 } 86 87 startCountdownIfNeeded(); 88} 89 90bool Watchdog::didFire(ExecState* exec) 91{ 92 if (m_didFire) 93 return true; 94 95 if (!m_timerDidFire) 96 return false; 97 m_timerDidFire = false; 98 stopCountdown(); 99 100 double currentTime = currentCPUTime(); 101 double deltaTime = currentTime - m_startTime; 102 double totalElapsedTime = m_elapsedTime + deltaTime; 103 if (totalElapsedTime > m_limit) { 104 // Case 1: the allowed CPU time has elapsed. 105 106 // If m_callback is not set, then we terminate by default. 107 // Else, we let m_callback decide if we should terminate or not. 108 bool needsTermination = !m_callback 109 || m_callback(exec, m_callbackData1, m_callbackData2); 110 if (needsTermination) { 111 m_didFire = true; 112 return true; 113 } 114 115 // The m_callback may have set a new limit. So, we may need to restart 116 // the countdown. 117 startCountdownIfNeeded(); 118 119 } else { 120 // Case 2: the allowed CPU time has NOT elapsed. 121 122 // Tell the timer to alarm us again when it thinks we've reached the 123 // end of the allowed time. 124 double remainingTime = m_limit - totalElapsedTime; 125 m_elapsedTime = totalElapsedTime; 126 m_startTime = currentTime; 127 startCountdown(remainingTime); 128 } 129 130 return false; 131} 132 133bool Watchdog::isEnabled() 134{ 135 return (m_limit != NO_LIMIT); 136} 137 138void Watchdog::fire() 139{ 140 m_didFire = true; 141} 142 143void Watchdog::arm() 144{ 145 m_reentryCount++; 146 if (m_reentryCount == 1) 147 startCountdownIfNeeded(); 148} 149 150void Watchdog::disarm() 151{ 152 ASSERT(m_reentryCount > 0); 153 if (m_reentryCount == 1) 154 stopCountdown(); 155 m_reentryCount--; 156} 157 158void Watchdog::startCountdownIfNeeded() 159{ 160 if (!m_isStopped) 161 return; // Already started. 162 163 if (!isArmed()) 164 return; // Not executing JS script. No need to start. 165 166 if (isEnabled()) { 167 m_elapsedTime = 0; 168 m_startTime = currentCPUTime(); 169 startCountdown(m_limit); 170 } 171} 172 173void Watchdog::startCountdown(double limit) 174{ 175 ASSERT(m_isStopped); 176 m_isStopped = false; 177 startTimer(limit); 178} 179 180void Watchdog::stopCountdown() 181{ 182 if (m_isStopped) 183 return; 184 stopTimer(); 185 m_isStopped = true; 186} 187 188} // namespace JSC 189