1/*
2 * Copyright (C) 2017-2018 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 "PromiseDeferredTimer.h"
28
29#include "JSPromiseDeferred.h"
30#include "StrongInlines.h"
31#include "VM.h"
32#include <wtf/Locker.h>
33#include <wtf/RunLoop.h>
34
35namespace JSC {
36
37namespace PromiseDeferredTimerInternal {
38static const bool verbose = false;
39}
40
41PromiseDeferredTimer::PromiseDeferredTimer(VM& vm)
42 : Base(&vm)
43{
44}
45
46void PromiseDeferredTimer::doWork(VM& vm)
47{
48 ASSERT(vm.currentThreadIsHoldingAPILock());
49 m_taskLock.lock();
50 cancelTimer();
51 if (!m_runTasks) {
52 m_taskLock.unlock();
53 return;
54 }
55
56 while (!m_tasks.isEmpty()) {
57 JSPromiseDeferred* ticket;
58 Task task;
59 std::tie(ticket, task) = m_tasks.takeLast();
60 dataLogLnIf(PromiseDeferredTimerInternal::verbose, "Doing work on promise: ", RawPointer(ticket));
61
62 // We may have already canceled these promises.
63 if (m_pendingPromises.contains(ticket)) {
64 // Allow tasks we run now to schedule work.
65 m_currentlyRunningTask = true;
66 m_taskLock.unlock();
67
68 task();
69 vm.drainMicrotasks();
70
71 m_taskLock.lock();
72 m_currentlyRunningTask = false;
73 }
74 }
75
76 if (m_pendingPromises.isEmpty() && m_shouldStopRunLoopWhenAllPromisesFinish) {
77#if USE(CF)
78 CFRunLoopStop(vm.runLoop());
79#else
80 RunLoop::current().stop();
81#endif
82 }
83
84 m_taskLock.unlock();
85}
86
87void PromiseDeferredTimer::runRunLoop()
88{
89 ASSERT(!m_apiLock->vm()->currentThreadIsHoldingAPILock());
90#if USE(CF)
91 ASSERT(CFRunLoopGetCurrent() == m_apiLock->vm()->runLoop());
92#endif
93 m_shouldStopRunLoopWhenAllPromisesFinish = true;
94 if (m_pendingPromises.size()) {
95#if USE(CF)
96 CFRunLoopRun();
97#else
98 RunLoop::run();
99#endif
100 }
101}
102
103void PromiseDeferredTimer::addPendingPromise(VM& vm, JSPromiseDeferred* ticket, Vector<Strong<JSCell>>&& dependencies)
104{
105 ASSERT(vm.currentThreadIsHoldingAPILock());
106 for (unsigned i = 0; i < dependencies.size(); ++i)
107 ASSERT(dependencies[i].get() != ticket);
108
109 auto result = m_pendingPromises.add(ticket, Vector<Strong<JSCell>>());
110 if (result.isNewEntry) {
111 dataLogLnIf(PromiseDeferredTimerInternal::verbose, "Adding new pending promise: ", RawPointer(ticket));
112 dependencies.append(Strong<JSCell>(vm, ticket));
113 result.iterator->value = WTFMove(dependencies);
114 } else {
115 dataLogLnIf(PromiseDeferredTimerInternal::verbose, "Adding new dependencies for promise: ", RawPointer(ticket));
116 result.iterator->value.appendVector(dependencies);
117 }
118
119#ifndef NDEBUG
120 ticket->promiseAsyncPending();
121#endif
122}
123
124bool PromiseDeferredTimer::hasPendingPromise(JSPromiseDeferred* ticket)
125{
126 ASSERT(ticket->vm()->currentThreadIsHoldingAPILock());
127 return m_pendingPromises.contains(ticket);
128}
129
130bool PromiseDeferredTimer::hasDependancyInPendingPromise(JSPromiseDeferred* ticket, JSCell* dependency)
131{
132 ASSERT(ticket->vm()->currentThreadIsHoldingAPILock());
133 ASSERT(m_pendingPromises.contains(ticket));
134
135 auto result = m_pendingPromises.get(ticket);
136 return result.contains(dependency);
137}
138
139bool PromiseDeferredTimer::cancelPendingPromise(JSPromiseDeferred* ticket)
140{
141 ASSERT(ticket->vm()->currentThreadIsHoldingAPILock());
142 bool result = m_pendingPromises.remove(ticket);
143
144 if (result)
145 dataLogLnIf(PromiseDeferredTimerInternal::verbose, "Canceling promise: ", RawPointer(ticket));
146
147 return result;
148}
149
150void PromiseDeferredTimer::scheduleWorkSoon(JSPromiseDeferred* ticket, Task&& task)
151{
152 LockHolder locker(m_taskLock);
153 m_tasks.append(std::make_tuple(ticket, WTFMove(task)));
154 if (!isScheduled() && !m_currentlyRunningTask)
155 setTimeUntilFire(0_s);
156}
157
158} // namespace JSC
159