summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/runtime/Watchdog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/runtime/Watchdog.cpp')
-rw-r--r--Source/JavaScriptCore/runtime/Watchdog.cpp223
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