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