/ runtime / DeferredWorkTimer.cpp
DeferredWorkTimer.cpp
  1  /*
  2   * Copyright (C) 2017-2020 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 "DeferredWorkTimer.h"
 28  
 29  #include "JSPromise.h"
 30  #include "StrongInlines.h"
 31  #include "VM.h"
 32  #include <wtf/RunLoop.h>
 33  
 34  namespace JSC {
 35  
 36  namespace DeferredWorkTimerInternal {
 37  static const bool verbose = false;
 38  }
 39  
 40  DeferredWorkTimer::DeferredWorkTimer(VM& vm)
 41      : Base(vm)
 42  {
 43  }
 44  
 45  void DeferredWorkTimer::doWork(VM& vm)
 46  {
 47      ASSERT(vm.currentThreadIsHoldingAPILock());
 48      auto locker = holdLock(m_taskLock);
 49      cancelTimer();
 50      if (!m_runTasks)
 51          return;
 52  
 53      Vector<std::tuple<Ticket, Task>> suspendedTasks;
 54  
 55      while (!m_tasks.isEmpty()) {
 56          auto [ticket, task] = m_tasks.takeFirst();
 57          auto globalObject = ticket->structure(vm)->globalObject();
 58          dataLogLnIf(DeferredWorkTimerInternal::verbose, "Doing work on: ", RawPointer(ticket));
 59  
 60          auto pendingTicket = m_pendingTickets.find(ticket);
 61          // We may have already canceled this task or its owner may have been canceled.
 62          if (pendingTicket == m_pendingTickets.end())
 63              continue;
 64  
 65          switch (globalObject->globalObjectMethodTable()->scriptExecutionStatus(globalObject, pendingTicket->value.scriptExecutionOwner.get())) {
 66          case ScriptExecutionStatus::Suspended:
 67              suspendedTasks.append(std::make_tuple(ticket, WTFMove(task)));
 68              continue;
 69          case ScriptExecutionStatus::Stopped:
 70              continue;
 71          case ScriptExecutionStatus::Running:
 72              break;
 73          }
 74  
 75          // Allow tasks we are about to run to schedule work.
 76          m_currentlyRunningTask = true;
 77          {
 78              auto dropper = DropLockForScope(locker);
 79  
 80              // This is the start of a runloop turn, we can release any weakrefs here.
 81              vm.finalizeSynchronousJSExecution();
 82  
 83              auto scope = DECLARE_CATCH_SCOPE(vm);
 84              task();
 85              if (Exception* exception = scope.exception()) {
 86                  auto* globalObject = ticket->globalObject();
 87                  scope.clearException();
 88                  globalObject->globalObjectMethodTable()->reportUncaughtExceptionAtEventLoop(globalObject, exception);
 89              }
 90  
 91              vm.drainMicrotasks();
 92              ASSERT(!vm.exceptionForInspection());
 93          }
 94          m_currentlyRunningTask = false;
 95      }
 96  
 97      while (!suspendedTasks.isEmpty())
 98          m_tasks.prepend(suspendedTasks.takeLast());
 99  
100      if (m_pendingTickets.isEmpty() && m_shouldStopRunLoopWhenAllTicketsFinish) {
101          ASSERT(m_tasks.isEmpty());
102          RunLoop::current().stop();
103      }
104  }
105  
106  void DeferredWorkTimer::runRunLoop()
107  {
108      ASSERT(!m_apiLock->vm()->currentThreadIsHoldingAPILock());
109      ASSERT(&RunLoop::current() == &m_apiLock->vm()->runLoop());
110      m_shouldStopRunLoopWhenAllTicketsFinish = true;
111      if (m_pendingTickets.size())
112          RunLoop::run();
113  }
114  
115  void DeferredWorkTimer::addPendingWork(VM& vm, Ticket ticket, Vector<Strong<JSCell>>&& dependencies)
116  {
117      ASSERT(vm.currentThreadIsHoldingAPILock() || (Thread::mayBeGCThread() && vm.heap.worldIsStopped()));
118      for (unsigned i = 0; i < dependencies.size(); ++i)
119          ASSERT(dependencies[i].get() != ticket);
120  
121      auto globalObject = ticket->globalObject();
122      auto result = m_pendingTickets.ensure(ticket, [&] {
123          dataLogLnIf(DeferredWorkTimerInternal::verbose, "Adding new pending ticket: ", RawPointer(ticket));
124          JSObject* scriptExecutionOwner = globalObject->globalObjectMethodTable()->currentScriptExecutionOwner(globalObject);
125          dependencies.append(Strong<JSCell>(vm, ticket));
126          return TicketData { WTFMove(dependencies), Strong<JSObject>(vm, scriptExecutionOwner) };
127      });
128      if (!result.isNewEntry) {
129          dataLogLnIf(DeferredWorkTimerInternal::verbose, "Adding new dependencies for ticket: ", RawPointer(ticket));
130          result.iterator->value.dependencies.appendVector(WTFMove(dependencies));
131      }
132  }
133  
134  bool DeferredWorkTimer::hasPendingWork(Ticket ticket)
135  {
136      ASSERT(ticket->vm().currentThreadIsHoldingAPILock() || (Thread::mayBeGCThread() && ticket->vm().heap.worldIsStopped()));
137      return m_pendingTickets.contains(ticket);
138  }
139  
140  bool DeferredWorkTimer::hasDependancyInPendingWork(Ticket ticket, JSCell* dependency)
141  {
142      ASSERT(ticket->vm().currentThreadIsHoldingAPILock() || (Thread::mayBeGCThread() && ticket->vm().heap.worldIsStopped()));
143      ASSERT(m_pendingTickets.contains(ticket));
144  
145      auto result = m_pendingTickets.get(ticket);
146      return result.dependencies.contains(dependency);
147  }
148  
149  bool DeferredWorkTimer::cancelPendingWork(Ticket ticket)
150  {
151      ASSERT(ticket->vm().currentThreadIsHoldingAPILock() || (Thread::mayBeGCThread() && ticket->vm().heap.worldIsStopped()));
152      bool result = m_pendingTickets.remove(ticket);
153  
154      if (result)
155          dataLogLnIf(DeferredWorkTimerInternal::verbose, "Canceling ticket: ", RawPointer(ticket));
156  
157      return result;
158  }
159  
160  void DeferredWorkTimer::scheduleWorkSoon(Ticket ticket, Task&& task)
161  {
162      LockHolder locker(m_taskLock);
163      m_tasks.append(std::make_tuple(ticket, WTFMove(task)));
164      if (!isScheduled() && !m_currentlyRunningTask)
165          setTimeUntilFire(0_s);
166  }
167  
168  void DeferredWorkTimer::didResumeScriptExecutionOwner()
169  {
170      ASSERT(!m_currentlyRunningTask);
171      LockHolder locker(m_taskLock);
172      if (!isScheduled() && m_tasks.size())
173          setTimeUntilFire(0_s);
174  }
175  
176  } // namespace JSC