2a78af8d02792e9324d718135bf2e80f49ef3a7a
[WebKit-https.git] / Source / JavaScriptCore / runtime / JSRunLoopTimer.cpp
1 /*
2  * Copyright (C) 2012-2018 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 "JSRunLoopTimer.h"
28
29 #include "IncrementalSweeper.h"
30 #include "JSCInlines.h"
31 #include "JSObject.h"
32 #include "JSString.h"
33
34 #include <wtf/MainThread.h>
35 #include <wtf/NoTailCalls.h>
36 #include <wtf/Threading.h>
37
38 #if USE(GLIB_EVENT_LOOP)
39 #include <glib.h>
40 #include <wtf/glib/RunLoopSourcePriority.h>
41 #endif
42
43 #include <mutex>
44
45 namespace JSC {
46
47 const Seconds JSRunLoopTimer::s_decade { 60 * 60 * 24 * 365 * 10 };
48
49 static inline JSRunLoopTimer::Manager::EpochTime epochTime(Seconds delay)
50 {
51 #if USE(CF)
52     return Seconds { CFAbsoluteTimeGetCurrent() + delay.value() };
53 #else
54     return MonotonicTime::now().secondsSinceEpoch() + delay;
55 #endif
56 }
57
58 #if USE(CF)
59 void JSRunLoopTimer::Manager::timerDidFireCallback(CFRunLoopTimerRef, void* contextPtr)
60 {
61     static_cast<JSRunLoopTimer::Manager*>(contextPtr)->timerDidFire();
62 }
63
64 void JSRunLoopTimer::Manager::PerVMData::setRunLoop(Manager* manager, CFRunLoopRef newRunLoop)
65 {
66     if (runLoop) {
67         CFRunLoopRemoveTimer(runLoop.get(), timer.get(), kCFRunLoopCommonModes);
68         CFRunLoopTimerInvalidate(timer.get());
69         runLoop.clear();
70         timer.clear();
71     }
72
73     if (newRunLoop) {
74         runLoop = newRunLoop;
75         memset(&context, 0, sizeof(CFRunLoopTimerContext));
76         RELEASE_ASSERT(manager);
77         context.info = manager;
78         timer = adoptCF(CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + s_decade.seconds(), CFAbsoluteTimeGetCurrent() + s_decade.seconds(), 0, 0, JSRunLoopTimer::Manager::timerDidFireCallback, &context));
79         CFRunLoopAddTimer(runLoop.get(), timer.get(), kCFRunLoopCommonModes);
80
81         EpochTime scheduleTime = epochTime(s_decade);
82         for (auto& pair : timers)
83             scheduleTime = std::min(pair.second, scheduleTime);
84         CFRunLoopTimerSetNextFireDate(timer.get(), scheduleTime.value());
85     }
86 }
87 #else
88 JSRunLoopTimer::Manager::PerVMData::PerVMData(Manager& manager)
89     : runLoop(&RunLoop::current())
90     , timer(std::make_unique<RunLoop::Timer<Manager>>(*runLoop, &manager, &JSRunLoopTimer::Manager::timerDidFireCallback))
91 {
92 #if USE(GLIB_EVENT_LOOP)
93     timer->setPriority(RunLoopSourcePriority::JavascriptTimer);
94     timer->setName("[JavaScriptCore] JSRunLoopTimer");
95 #endif
96 }
97
98 void JSRunLoopTimer::Manager::timerDidFireCallback()
99 {
100     timerDidFire();
101 }
102 #endif
103
104 JSRunLoopTimer::Manager::PerVMData::~PerVMData()
105 {
106 #if USE(CF)
107     setRunLoop(nullptr, nullptr);
108 #endif
109 }
110
111 void JSRunLoopTimer::Manager::timerDidFire()
112 {
113     Vector<Ref<JSRunLoopTimer>> timersToFire;
114
115     {
116         auto locker = holdLock(m_lock);
117 #if USE(CF)
118         CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
119 #else
120         RunLoop* currentRunLoop = &RunLoop::current();
121 #endif
122         EpochTime nowEpochTime = epochTime(0_s);
123         for (auto& entry : m_mapping) {
124             PerVMData& data = entry.value;
125 #if USE(CF)
126             if (data.runLoop.get() != currentRunLoop)
127                 continue;
128 #else
129             if (data.runLoop != currentRunLoop)
130                 continue;
131 #endif
132             
133             EpochTime scheduleTime = epochTime(s_decade);
134             for (size_t i = 0; i < data.timers.size(); ++i) {
135                 {
136                     auto& pair = data.timers[i];
137                     if (pair.second > nowEpochTime) {
138                         scheduleTime = std::min(pair.second, scheduleTime);
139                         continue;
140                     }
141                     auto& last = data.timers.last();
142                     if (&last != &pair)
143                         std::swap(pair, last);
144                     --i;
145                 }
146
147                 auto pair = data.timers.takeLast();
148                 timersToFire.append(WTFMove(pair.first));
149             }
150
151 #if USE(CF)
152             CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
153 #else
154             data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
155 #endif
156         }
157     }
158
159     for (auto& timer : timersToFire)
160         timer->timerDidFire();
161 }
162
163 JSRunLoopTimer::Manager& JSRunLoopTimer::Manager::shared()
164 {
165     static Manager* manager;
166     static std::once_flag once;
167     std::call_once(once, [&] {
168         manager = new Manager;
169     });
170     return *manager;
171 }
172
173 void JSRunLoopTimer::Manager::registerVM(VM& vm)
174 {
175     PerVMData data { *this };
176 #if USE(CF)
177     data.setRunLoop(this, vm.runLoop());
178 #endif
179
180     auto locker = holdLock(m_lock);
181     auto addResult = m_mapping.add({ vm.apiLock() }, WTFMove(data));
182     RELEASE_ASSERT(addResult.isNewEntry);
183 }
184
185 void JSRunLoopTimer::Manager::unregisterVM(VM& vm)
186 {
187     auto locker = holdLock(m_lock);
188
189     auto iter = m_mapping.find({ vm.apiLock() });
190     RELEASE_ASSERT(iter != m_mapping.end());
191     m_mapping.remove(iter);
192 }
193
194 void JSRunLoopTimer::Manager::scheduleTimer(JSRunLoopTimer& timer, Seconds delay)
195 {
196     EpochTime fireEpochTime = epochTime(delay);
197
198     auto locker = holdLock(m_lock);
199     auto iter = m_mapping.find(timer.m_apiLock);
200     RELEASE_ASSERT(iter != m_mapping.end()); // We don't allow calling this after the VM dies.
201
202     PerVMData& data = iter->value;
203     EpochTime scheduleTime = fireEpochTime;
204     bool found = false;
205     for (auto& entry : data.timers) {
206         if (entry.first.ptr() == &timer) {
207             entry.second = fireEpochTime;
208             found = true;
209         }
210         scheduleTime = std::min(scheduleTime, entry.second);
211     }
212
213     if (!found)
214         data.timers.append({ timer, fireEpochTime });
215
216 #if USE(CF)
217     CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
218 #else
219     data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
220 #endif
221 }
222
223 void JSRunLoopTimer::Manager::cancelTimer(JSRunLoopTimer& timer)
224 {
225     auto locker = holdLock(m_lock);
226     auto iter = m_mapping.find(timer.m_apiLock);
227     if (iter == m_mapping.end()) {
228         // It's trivial to allow this to be called after the VM dies, so we allow for it.
229         return;
230     }
231
232     PerVMData& data = iter->value;
233     EpochTime scheduleTime = epochTime(s_decade);
234     for (unsigned i = 0; i < data.timers.size(); ++i) {
235         {
236             auto& entry = data.timers[i];
237             if (entry.first.ptr() == &timer) {
238                 RELEASE_ASSERT(timer.refCount() >= 2); // If we remove it from the entry below, we should not be the last thing pointing to it!
239                 auto& last = data.timers.last();
240                 if (&last != &entry)
241                     std::swap(entry, last);
242                 data.timers.removeLast();
243                 i--;
244                 continue;
245             }
246         }
247
248         scheduleTime = std::min(scheduleTime, data.timers[i].second);
249     }
250
251 #if USE(CF)
252     CFRunLoopTimerSetNextFireDate(data.timer.get(), scheduleTime.value());
253 #else
254     data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch()));
255 #endif
256 }
257
258 Optional<Seconds> JSRunLoopTimer::Manager::timeUntilFire(JSRunLoopTimer& timer)
259 {
260     auto locker = holdLock(m_lock);
261     auto iter = m_mapping.find(timer.m_apiLock);
262     RELEASE_ASSERT(iter != m_mapping.end()); // We only allow this to be called with a live VM.
263
264     PerVMData& data = iter->value;
265     for (auto& entry : data.timers) {
266         if (entry.first.ptr() == &timer) {
267             EpochTime nowEpochTime = epochTime(0_s);
268             return entry.second - nowEpochTime;
269         }
270     }
271
272     return WTF::nullopt;
273 }
274
275 #if USE(CF)
276 void JSRunLoopTimer::Manager::didChangeRunLoop(VM& vm, CFRunLoopRef newRunLoop)
277 {
278     auto locker = holdLock(m_lock);
279     auto iter = m_mapping.find({ vm.apiLock() });
280     RELEASE_ASSERT(iter != m_mapping.end());
281
282     PerVMData& data = iter->value;
283     data.setRunLoop(this, newRunLoop);
284 }
285 #endif
286
287 void JSRunLoopTimer::timerDidFire()
288 {
289     NO_TAIL_CALLS();
290
291     {
292         auto locker = holdLock(m_lock);
293         if (!m_isScheduled) {
294             // We raced between this callback being called and cancel() being called.
295             // That's fine, we just don't do anything here.
296             return;
297         }
298     }
299
300     std::lock_guard<JSLock> lock(m_apiLock.get());
301     RefPtr<VM> vm = m_apiLock->vm();
302     if (!vm) {
303         // The VM has been destroyed, so we should just give up.
304         return;
305     }
306
307     doWork(*vm);
308 }
309
310 JSRunLoopTimer::JSRunLoopTimer(VM* vm)
311     : m_apiLock(vm->apiLock())
312 {
313 }
314
315 JSRunLoopTimer::~JSRunLoopTimer()
316 {
317 }
318
319 Optional<Seconds> JSRunLoopTimer::timeUntilFire()
320 {
321     return Manager::shared().timeUntilFire(*this);
322 }
323
324 void JSRunLoopTimer::setTimeUntilFire(Seconds intervalInSeconds)
325 {
326     {
327         auto locker = holdLock(m_lock);
328         m_isScheduled = true;
329         Manager::shared().scheduleTimer(*this, intervalInSeconds);
330     }
331
332     auto locker = holdLock(m_timerCallbacksLock);
333     for (auto& task : m_timerSetCallbacks)
334         task->run();
335 }
336
337 void JSRunLoopTimer::cancelTimer()
338 {
339     auto locker = holdLock(m_lock);
340     m_isScheduled = false;
341     Manager::shared().cancelTimer(*this);
342 }
343
344 void JSRunLoopTimer::addTimerSetNotification(TimerNotificationCallback callback)
345 {
346     auto locker = holdLock(m_timerCallbacksLock);
347     m_timerSetCallbacks.add(callback);
348 }
349
350 void JSRunLoopTimer::removeTimerSetNotification(TimerNotificationCallback callback)
351 {
352     auto locker = holdLock(m_timerCallbacksLock);
353     m_timerSetCallbacks.remove(callback);
354 }
355
356 } // namespace JSC