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
45namespace JSC {
46
47const Seconds JSRunLoopTimer::s_decade { 60 * 60 * 24 * 365 * 10 };
48
49static 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)
59void JSRunLoopTimer::Manager::timerDidFireCallback(CFRunLoopTimerRef, void* contextPtr)
60{
61 static_cast<JSRunLoopTimer::Manager*>(contextPtr)->timerDidFire();
62}
63
64void 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
88JSRunLoopTimer::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
98void JSRunLoopTimer::Manager::timerDidFireCallback()
99{
100 timerDidFire();
101}
102#endif
103
104JSRunLoopTimer::Manager::PerVMData::~PerVMData()
105{
106#if USE(CF)
107 setRunLoop(nullptr, nullptr);
108#endif
109}
110
111void 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
163JSRunLoopTimer::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
173void JSRunLoopTimer::Manager::registerVM(VM& vm)
174{
175 auto data = std::make_unique<PerVMData>(*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
185void 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
194void 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
223void 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
258Optional<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)
276void 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
287void 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
310JSRunLoopTimer::JSRunLoopTimer(VM* vm)
311 : m_apiLock(vm->apiLock())
312{
313}
314
315JSRunLoopTimer::~JSRunLoopTimer()
316{
317}
318
319Optional<Seconds> JSRunLoopTimer::timeUntilFire()
320{
321 return Manager::shared().timeUntilFire(*this);
322}
323
324void 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
337void JSRunLoopTimer::cancelTimer()
338{
339 auto locker = holdLock(m_lock);
340 m_isScheduled = false;
341 Manager::shared().cancelTimer(*this);
342}
343
344void JSRunLoopTimer::addTimerSetNotification(TimerNotificationCallback callback)
345{
346 auto locker = holdLock(m_timerCallbacksLock);
347 m_timerSetCallbacks.add(callback);
348}
349
350void JSRunLoopTimer::removeTimerSetNotification(TimerNotificationCallback callback)
351{
352 auto locker = holdLock(m_timerCallbacksLock);
353 m_timerSetCallbacks.remove(callback);
354}
355
356} // namespace JSC
357