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