267121c2fd2fbdcd1866bd1ecb9b205cd4218bff
[WebKit-https.git] / Source / JavaScriptCore / runtime / Watchdog.cpp
1 /*
2  * Copyright (C) 2013-2017 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
33 namespace JSC {
34
35 const std::chrono::microseconds Watchdog::noTimeLimit = std::chrono::microseconds::max();
36
37 static std::chrono::microseconds currentWallClockTime()
38 {
39     auto steadyTimeSinceEpoch = std::chrono::steady_clock::now().time_since_epoch();
40     return std::chrono::duration_cast<std::chrono::microseconds>(steadyTimeSinceEpoch);
41 }
42
43 Watchdog::Watchdog(VM* vm)
44     : m_vm(vm)
45     , m_timeLimit(noTimeLimit)
46     , m_cpuDeadline(noTimeLimit)
47     , m_wallClockDeadline(noTimeLimit)
48     , m_callback(0)
49     , m_callbackData1(0)
50     , m_callbackData2(0)
51     , m_timerQueue(WorkQueue::create("jsc.watchdog.queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility))
52 {
53 }
54
55 void Watchdog::setTimeLimit(std::chrono::microseconds limit,
56     ShouldTerminateCallback callback, void* data1, void* data2)
57 {
58     ASSERT(m_vm->currentThreadIsHoldingAPILock());
59
60     m_timeLimit = limit;
61     m_callback = callback;
62     m_callbackData1 = data1;
63     m_callbackData2 = data2;
64
65     if (m_hasEnteredVM && hasTimeLimit())
66         startTimer(m_timeLimit);
67 }
68
69 bool Watchdog::shouldTerminate(ExecState* exec)
70 {
71     ASSERT(m_vm->currentThreadIsHoldingAPILock());
72     if (currentWallClockTime() < m_wallClockDeadline)
73         return false; // Just a stale timer firing. Nothing to do.
74
75     // Set m_wallClockDeadline to noTimeLimit here so that we can reject all future
76     // spurious wakes.
77     m_wallClockDeadline = noTimeLimit;
78
79     auto cpuTime = currentCPUTime();
80     if (cpuTime < m_cpuDeadline) {
81         auto remainingCPUTime = m_cpuDeadline - cpuTime;
82         startTimer(remainingCPUTime);
83         return false;
84     }
85
86     // Note: we should not be holding the lock while calling the callbacks. The callbacks may
87     // call setTimeLimit() which will try to lock as well.
88
89     // If m_callback is not set, then we terminate by default.
90     // Else, we let m_callback decide if we should terminate or not.
91     bool needsTermination = !m_callback
92         || m_callback(exec, m_callbackData1, m_callbackData2);
93     if (needsTermination)
94         return true;
95
96     // If we get here, then the callback above did not want to terminate execution. As a
97     // result, the callback may have done one of the following:
98     //   1. cleared the time limit (i.e. watchdog is disabled),
99     //   2. set a new time limit via Watchdog::setTimeLimit(), or
100     //   3. did nothing (i.e. allow another cycle of the current time limit).
101     //
102     // In the case of 1, we don't have to do anything.
103     // In the case of 2, Watchdog::setTimeLimit() would already have started the timer.
104     // In the case of 3, we need to re-start the timer here.
105
106     ASSERT(m_hasEnteredVM);
107     bool callbackAlreadyStartedTimer = (m_cpuDeadline != noTimeLimit);
108     if (hasTimeLimit() && !callbackAlreadyStartedTimer)
109         startTimer(m_timeLimit);
110
111     return false;
112 }
113
114 bool Watchdog::hasTimeLimit()
115 {
116     return (m_timeLimit != noTimeLimit);
117 }
118
119 void Watchdog::enteredVM()
120 {
121     m_hasEnteredVM = true;
122     if (hasTimeLimit())
123         startTimer(m_timeLimit);
124 }
125
126 void Watchdog::exitedVM()
127 {
128     ASSERT(m_hasEnteredVM);
129     stopTimer();
130     m_hasEnteredVM = false;
131 }
132
133 void Watchdog::startTimer(std::chrono::microseconds timeLimit)
134 {
135     ASSERT(m_hasEnteredVM);
136     ASSERT(m_vm->currentThreadIsHoldingAPILock());
137     ASSERT(hasTimeLimit());
138     ASSERT(timeLimit <= m_timeLimit);
139
140     m_cpuDeadline = currentCPUTime() + timeLimit;
141     auto wallClockTime = currentWallClockTime();
142     auto wallClockDeadline = wallClockTime + timeLimit;
143
144     if ((wallClockTime < m_wallClockDeadline)
145         && (m_wallClockDeadline <= wallClockDeadline))
146         return; // Wait for the current active timer to expire before starting a new one.
147
148     // Else, the current active timer won't fire soon enough. So, start a new timer.
149     m_wallClockDeadline = wallClockDeadline;
150
151     // We need to ensure that the Watchdog outlives the timer.
152     // For the same reason, the timer may also outlive the VM that the Watchdog operates on.
153     // So, we always need to null check m_vm before using it. The VM will notify the Watchdog
154     // via willDestroyVM() before it goes away.
155     RefPtr<Watchdog> protectedThis = this;
156     m_timerQueue->dispatchAfter(Seconds::fromMicroseconds(timeLimit.count()), [this, protectedThis] {
157         LockHolder locker(m_lock);
158         if (m_vm)
159             m_vm->notifyNeedWatchdogCheck();
160     });
161 }
162
163 void Watchdog::stopTimer()
164 {
165     ASSERT(m_hasEnteredVM);
166     ASSERT(m_vm->currentThreadIsHoldingAPILock());
167     m_cpuDeadline = noTimeLimit;
168 }
169
170 void Watchdog::willDestroyVM(VM* vm)
171 {
172     LockHolder locker(m_lock);
173     ASSERT_UNUSED(vm, m_vm == vm);
174     m_vm = nullptr;
175 }
176
177 } // namespace JSC