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