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