/ runtime / JSRunLoopTimer.cpp
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