5734263fd1f1379c054f6026d03a127c793dfe07
[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     // FIXME: Will unindent the following before landing. Leaving indented for now to minimize the code diff.
73     {
74         if (currentWallClockTime() < m_wallClockDeadline)
75             return false; // Just a stale timer firing. Nothing to do.
76
77         // Set m_wallClockDeadline to noTimeLimit here so that we can reject all future
78         // spurious wakes.
79         m_wallClockDeadline = noTimeLimit;
80
81         auto cpuTime = currentCPUTime();
82         if (cpuTime < m_cpuDeadline) {
83             auto remainingCPUTime = m_cpuDeadline - cpuTime;
84             startTimer(remainingCPUTime);
85             return false;
86         }
87     }
88
89     // Note: we should not be holding the lock while calling the callbacks. The callbacks may
90     // call setTimeLimit() which will try to lock as well.
91
92     // If m_callback is not set, then we terminate by default.
93     // Else, we let m_callback decide if we should terminate or not.
94     bool needsTermination = !m_callback
95         || m_callback(exec, m_callbackData1, m_callbackData2);
96     if (needsTermination)
97         return true;
98
99     // FIXME: Will unindent the following before landing. Leaving indented for now to minimize the code diff.
100     {
101         // If we get here, then the callback above did not want to terminate execution. As a
102         // result, the callback may have done one of the following:
103         //   1. cleared the time limit (i.e. watchdog is disabled),
104         //   2. set a new time limit via Watchdog::setTimeLimit(), or
105         //   3. did nothing (i.e. allow another cycle of the current time limit).
106         //
107         // In the case of 1, we don't have to do anything.
108         // In the case of 2, Watchdog::setTimeLimit() would already have started the timer.
109         // In the case of 3, we need to re-start the timer here.
110
111         ASSERT(m_hasEnteredVM);
112         bool callbackAlreadyStartedTimer = (m_cpuDeadline != noTimeLimit);
113         if (hasTimeLimit() && !callbackAlreadyStartedTimer)
114             startTimer(m_timeLimit);
115     }
116     return false;
117 }
118
119 bool Watchdog::hasTimeLimit()
120 {
121     return (m_timeLimit != noTimeLimit);
122 }
123
124 void Watchdog::enteredVM()
125 {
126     m_hasEnteredVM = true;
127     if (hasTimeLimit())
128         startTimer(m_timeLimit);
129 }
130
131 void Watchdog::exitedVM()
132 {
133     ASSERT(m_hasEnteredVM);
134     stopTimer();
135     m_hasEnteredVM = false;
136 }
137
138 void Watchdog::startTimer(std::chrono::microseconds timeLimit)
139 {
140     ASSERT(m_hasEnteredVM);
141     ASSERT(m_vm->currentThreadIsHoldingAPILock());
142     ASSERT(hasTimeLimit());
143     ASSERT(timeLimit <= m_timeLimit);
144
145     m_cpuDeadline = currentCPUTime() + timeLimit;
146     auto wallClockTime = currentWallClockTime();
147     auto wallClockDeadline = wallClockTime + timeLimit;
148
149     if ((wallClockTime < m_wallClockDeadline)
150         && (m_wallClockDeadline <= wallClockDeadline))
151         return; // Wait for the current active timer to expire before starting a new one.
152
153     // Else, the current active timer won't fire soon enough. So, start a new timer.
154     m_wallClockDeadline = wallClockDeadline;
155
156     // We need to ensure that the Watchdog outlives the timer.
157     // For the same reason, the timer may also outlive the VM that the Watchdog operates on.
158     // So, we always need to null check m_vm before using it. The VM will notify the Watchdog
159     // via willDestroyVM() before it goes away.
160     RefPtr<Watchdog> protectedThis = this;
161     m_timerQueue->dispatchAfter(Seconds::fromMicroseconds(timeLimit.count()), [this, protectedThis] {
162         LockHolder locker(m_lock);
163         if (m_vm)
164             m_vm->notifyNeedWatchdogCheck();
165     });
166 }
167
168 void Watchdog::stopTimer()
169 {
170     ASSERT(m_hasEnteredVM);
171     ASSERT(m_vm->currentThreadIsHoldingAPILock());
172     m_cpuDeadline = noTimeLimit;
173 }
174
175 void Watchdog::willDestroyVM(VM* vm)
176 {
177     LockHolder locker(m_lock);
178     ASSERT_UNUSED(vm, m_vm == vm);
179     m_vm = nullptr;
180 }
181
182 } // namespace JSC