JSRunLoopTimer.cpp
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 "VM.h" 31 #include <mutex> 32 #include <wtf/NoTailCalls.h> 33 34 #if USE(GLIB_EVENT_LOOP) 35 #include <glib.h> 36 #include <wtf/glib/RunLoopSourcePriority.h> 37 #endif 38 39 namespace JSC { 40 41 static inline JSRunLoopTimer::Manager::EpochTime epochTime(Seconds delay) 42 { 43 return MonotonicTime::now().secondsSinceEpoch() + delay; 44 } 45 46 JSRunLoopTimer::Manager::PerVMData::PerVMData(Manager& manager, RunLoop& runLoop) 47 : runLoop(runLoop) 48 , timer(makeUnique<RunLoop::Timer<Manager>>(runLoop, &manager, &JSRunLoopTimer::Manager::timerDidFireCallback)) 49 { 50 #if USE(GLIB_EVENT_LOOP) 51 timer->setPriority(RunLoopSourcePriority::JavascriptTimer); 52 timer->setName("[JavaScriptCore] JSRunLoopTimer"); 53 #endif 54 } 55 56 void JSRunLoopTimer::Manager::timerDidFireCallback() 57 { 58 timerDidFire(); 59 } 60 61 JSRunLoopTimer::Manager::PerVMData::~PerVMData() 62 { 63 // Because RunLoop::Timer is not reference counted, we need to deallocate it 64 // on the same thread on which it fires; otherwise, we might deallocate it 65 // while it's firing. 66 runLoop->dispatch([timer = WTFMove(timer)] { 67 }); 68 } 69 70 void JSRunLoopTimer::Manager::timerDidFire() 71 { 72 Vector<Ref<JSRunLoopTimer>> timersToFire; 73 74 { 75 auto locker = holdLock(m_lock); 76 RunLoop* currentRunLoop = &RunLoop::current(); 77 EpochTime nowEpochTime = epochTime(0_s); 78 for (auto& entry : m_mapping) { 79 PerVMData& data = *entry.value; 80 if (data.runLoop.ptr() != currentRunLoop) 81 continue; 82 83 EpochTime scheduleTime = epochTime(s_decade); 84 for (size_t i = 0; i < data.timers.size(); ++i) { 85 { 86 auto& pair = data.timers[i]; 87 if (pair.second > nowEpochTime) { 88 scheduleTime = std::min(pair.second, scheduleTime); 89 continue; 90 } 91 auto& last = data.timers.last(); 92 if (&last != &pair) 93 std::swap(pair, last); 94 --i; 95 } 96 97 auto pair = data.timers.takeLast(); 98 timersToFire.append(WTFMove(pair.first)); 99 } 100 101 data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch())); 102 } 103 } 104 105 for (auto& timer : timersToFire) 106 timer->timerDidFire(); 107 } 108 109 JSRunLoopTimer::Manager& JSRunLoopTimer::Manager::shared() 110 { 111 static Manager* manager; 112 static std::once_flag once; 113 std::call_once(once, [&] { 114 manager = new Manager; 115 }); 116 return *manager; 117 } 118 119 void JSRunLoopTimer::Manager::registerVM(VM& vm) 120 { 121 auto data = makeUnique<PerVMData>(*this, vm.runLoop()); 122 123 auto locker = holdLock(m_lock); 124 auto addResult = m_mapping.add({ vm.apiLock() }, WTFMove(data)); 125 RELEASE_ASSERT(addResult.isNewEntry); 126 } 127 128 void JSRunLoopTimer::Manager::unregisterVM(VM& vm) 129 { 130 auto locker = holdLock(m_lock); 131 132 auto iter = m_mapping.find({ vm.apiLock() }); 133 RELEASE_ASSERT(iter != m_mapping.end()); 134 m_mapping.remove(iter); 135 } 136 137 void JSRunLoopTimer::Manager::scheduleTimer(JSRunLoopTimer& timer, Seconds delay) 138 { 139 EpochTime fireEpochTime = epochTime(delay); 140 141 auto locker = holdLock(m_lock); 142 auto iter = m_mapping.find(timer.m_apiLock); 143 RELEASE_ASSERT(iter != m_mapping.end()); // We don't allow calling this after the VM dies. 144 145 PerVMData& data = *iter->value; 146 EpochTime scheduleTime = fireEpochTime; 147 bool found = false; 148 for (auto& entry : data.timers) { 149 if (entry.first.ptr() == &timer) { 150 entry.second = fireEpochTime; 151 found = true; 152 } 153 scheduleTime = std::min(scheduleTime, entry.second); 154 } 155 156 if (!found) 157 data.timers.append({ timer, fireEpochTime }); 158 159 data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch())); 160 } 161 162 void JSRunLoopTimer::Manager::cancelTimer(JSRunLoopTimer& timer) 163 { 164 auto locker = holdLock(m_lock); 165 auto iter = m_mapping.find(timer.m_apiLock); 166 if (iter == m_mapping.end()) { 167 // It's trivial to allow this to be called after the VM dies, so we allow for it. 168 return; 169 } 170 171 PerVMData& data = *iter->value; 172 EpochTime scheduleTime = epochTime(s_decade); 173 for (unsigned i = 0; i < data.timers.size(); ++i) { 174 { 175 auto& entry = data.timers[i]; 176 if (entry.first.ptr() == &timer) { 177 RELEASE_ASSERT(timer.refCount() >= 2); // If we remove it from the entry below, we should not be the last thing pointing to it! 178 auto& last = data.timers.last(); 179 if (&last != &entry) 180 std::swap(entry, last); 181 data.timers.removeLast(); 182 i--; 183 continue; 184 } 185 } 186 187 scheduleTime = std::min(scheduleTime, data.timers[i].second); 188 } 189 190 data.timer->startOneShot(std::max(0_s, scheduleTime - MonotonicTime::now().secondsSinceEpoch())); 191 } 192 193 Optional<Seconds> JSRunLoopTimer::Manager::timeUntilFire(JSRunLoopTimer& timer) 194 { 195 auto locker = holdLock(m_lock); 196 auto iter = m_mapping.find(timer.m_apiLock); 197 RELEASE_ASSERT(iter != m_mapping.end()); // We only allow this to be called with a live VM. 198 199 PerVMData& data = *iter->value; 200 for (auto& entry : data.timers) { 201 if (entry.first.ptr() == &timer) { 202 EpochTime nowEpochTime = epochTime(0_s); 203 return entry.second - nowEpochTime; 204 } 205 } 206 207 return WTF::nullopt; 208 } 209 210 void JSRunLoopTimer::timerDidFire() 211 { 212 NO_TAIL_CALLS(); 213 214 { 215 auto locker = holdLock(m_lock); 216 if (!m_isScheduled) { 217 // We raced between this callback being called and cancel() being called. 218 // That's fine, we just don't do anything here. 219 return; 220 } 221 } 222 223 auto locker = holdLock(m_apiLock.get()); 224 RefPtr<VM> vm = m_apiLock->vm(); 225 if (!vm) { 226 // The VM has been destroyed, so we should just give up. 227 return; 228 } 229 230 doWork(*vm); 231 } 232 233 JSRunLoopTimer::JSRunLoopTimer(VM& vm) 234 : m_apiLock(vm.apiLock()) 235 { 236 } 237 238 JSRunLoopTimer::~JSRunLoopTimer() 239 { 240 } 241 242 Optional<Seconds> JSRunLoopTimer::timeUntilFire() 243 { 244 return Manager::shared().timeUntilFire(*this); 245 } 246 247 void JSRunLoopTimer::setTimeUntilFire(Seconds intervalInSeconds) 248 { 249 { 250 auto locker = holdLock(m_lock); 251 m_isScheduled = true; 252 Manager::shared().scheduleTimer(*this, intervalInSeconds); 253 } 254 255 auto locker = holdLock(m_timerCallbacksLock); 256 for (auto& task : m_timerSetCallbacks) 257 task->run(); 258 } 259 260 void JSRunLoopTimer::cancelTimer() 261 { 262 auto locker = holdLock(m_lock); 263 m_isScheduled = false; 264 Manager::shared().cancelTimer(*this); 265 } 266 267 void JSRunLoopTimer::addTimerSetNotification(TimerNotificationCallback callback) 268 { 269 auto locker = holdLock(m_timerCallbacksLock); 270 m_timerSetCallbacks.add(callback); 271 } 272 273 void JSRunLoopTimer::removeTimerSetNotification(TimerNotificationCallback callback) 274 { 275 auto locker = holdLock(m_timerCallbacksLock); 276 m_timerSetCallbacks.remove(callback); 277 } 278 279 } // namespace JSC