1/*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33
34#include "ScriptExecutionContext.h"
35#include "SharedTimer.h"
36#include "ThreadGlobalData.h"
37#include "ThreadTimers.h"
38#include "WorkerRunLoop.h"
39#include "WorkerGlobalScope.h"
40#include "WorkerThread.h"
41#include <JavaScriptCore/PromiseDeferredTimer.h>
42
43#if USE(GLIB)
44#include <glib.h>
45#endif
46
47namespace WebCore {
48
49class WorkerSharedTimer final : public SharedTimer {
50public:
51 // SharedTimer interface.
52 void setFiredFunction(WTF::Function<void()>&& function) final { m_sharedTimerFunction = WTFMove(function); }
53 void setFireInterval(Seconds interval) final { m_nextFireTime = MonotonicTime::now() + interval; }
54 void stop() final { m_nextFireTime = MonotonicTime { }; }
55
56 bool isActive() { return m_sharedTimerFunction && m_nextFireTime; }
57 Seconds fireTimeDelay() { return std::max(0_s, m_nextFireTime - MonotonicTime::now()); }
58 void fire() { m_sharedTimerFunction(); }
59
60private:
61 WTF::Function<void()> m_sharedTimerFunction;
62 MonotonicTime m_nextFireTime;
63};
64
65class ModePredicate {
66public:
67 explicit ModePredicate(String&& mode)
68 : m_mode(WTFMove(mode))
69 , m_defaultMode(m_mode == WorkerRunLoop::defaultMode())
70 {
71 }
72
73 bool isDefaultMode() const
74 {
75 return m_defaultMode;
76 }
77
78 bool operator()(const WorkerRunLoop::Task& task) const
79 {
80 return m_defaultMode || m_mode == task.mode();
81 }
82
83private:
84 String m_mode;
85 bool m_defaultMode;
86};
87
88WorkerRunLoop::WorkerRunLoop()
89 : m_sharedTimer(std::make_unique<WorkerSharedTimer>())
90{
91}
92
93WorkerRunLoop::~WorkerRunLoop()
94{
95 ASSERT(!m_nestedCount);
96}
97
98String WorkerRunLoop::defaultMode()
99{
100 return String();
101}
102
103static String debuggerMode()
104{
105 return "debugger"_s;
106}
107
108class RunLoopSetup {
109 WTF_MAKE_NONCOPYABLE(RunLoopSetup);
110public:
111 enum class IsForDebugging { No, Yes };
112 RunLoopSetup(WorkerRunLoop& runLoop, IsForDebugging isForDebugging)
113 : m_runLoop(runLoop)
114 , m_isForDebugging(isForDebugging)
115 {
116 if (!m_runLoop.m_nestedCount)
117 threadGlobalData().threadTimers().setSharedTimer(m_runLoop.m_sharedTimer.get());
118 m_runLoop.m_nestedCount++;
119 if (m_isForDebugging == IsForDebugging::Yes)
120 m_runLoop.m_debugCount++;
121 }
122
123 ~RunLoopSetup()
124 {
125 m_runLoop.m_nestedCount--;
126 if (!m_runLoop.m_nestedCount)
127 threadGlobalData().threadTimers().setSharedTimer(nullptr);
128 if (m_isForDebugging == IsForDebugging::Yes)
129 m_runLoop.m_debugCount--;
130 }
131private:
132 WorkerRunLoop& m_runLoop;
133 IsForDebugging m_isForDebugging { IsForDebugging::No };
134};
135
136void WorkerRunLoop::run(WorkerGlobalScope* context)
137{
138 RunLoopSetup setup(*this, RunLoopSetup::IsForDebugging::No);
139 ModePredicate modePredicate(defaultMode());
140 MessageQueueWaitResult result;
141 do {
142 result = runInMode(context, modePredicate, WaitForMessage);
143 } while (result != MessageQueueTerminated);
144 runCleanupTasks(context);
145}
146
147MessageQueueWaitResult WorkerRunLoop::runInDebuggerMode(WorkerGlobalScope& context)
148{
149 RunLoopSetup setup(*this, RunLoopSetup::IsForDebugging::Yes);
150 return runInMode(&context, ModePredicate { debuggerMode() }, WaitForMessage);
151}
152
153MessageQueueWaitResult WorkerRunLoop::runInMode(WorkerGlobalScope* context, const String& mode, WaitMode waitMode)
154{
155 ASSERT(mode != debuggerMode());
156 RunLoopSetup setup(*this, RunLoopSetup::IsForDebugging::No);
157 ModePredicate modePredicate(String { mode });
158 MessageQueueWaitResult result = runInMode(context, modePredicate, waitMode);
159 return result;
160}
161
162MessageQueueWaitResult WorkerRunLoop::runInMode(WorkerGlobalScope* context, const ModePredicate& predicate, WaitMode waitMode)
163{
164 ASSERT(context);
165 ASSERT(context->thread().thread() == &Thread::current());
166
167 JSC::JSRunLoopTimer::TimerNotificationCallback timerAddedTask = WTF::createSharedTask<JSC::JSRunLoopTimer::TimerNotificationType>([this] {
168 // We don't actually do anything here, we just want to loop around runInMode
169 // to both recalculate our deadline and to potentially run the run loop.
170 this->postTask([](ScriptExecutionContext&) { });
171 });
172
173#if USE(GLIB)
174 GMainContext* mainContext = g_main_context_get_thread_default();
175 if (g_main_context_pending(mainContext))
176 g_main_context_iteration(mainContext, FALSE);
177#endif
178
179 Seconds timeoutDelay = Seconds::infinity();
180
181#if USE(CF)
182 CFAbsoluteTime nextCFRunLoopTimerFireDate = CFRunLoopGetNextTimerFireDate(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
183 double timeUntilNextCFRunLoopTimerInSeconds = nextCFRunLoopTimerFireDate - CFAbsoluteTimeGetCurrent();
184 timeoutDelay = std::max(0_s, Seconds(timeUntilNextCFRunLoopTimerInSeconds));
185#endif
186
187 if (waitMode == WaitForMessage && predicate.isDefaultMode() && m_sharedTimer->isActive())
188 timeoutDelay = std::min(timeoutDelay, m_sharedTimer->fireTimeDelay());
189
190 if (WorkerScriptController* script = context->script()) {
191 script->releaseHeapAccess();
192 script->addTimerSetNotification(timerAddedTask);
193 }
194 MessageQueueWaitResult result;
195 auto task = m_messageQueue.waitForMessageFilteredWithTimeout(result, predicate, timeoutDelay);
196 if (WorkerScriptController* script = context->script()) {
197 script->acquireHeapAccess();
198 script->removeTimerSetNotification(timerAddedTask);
199 }
200
201 // If the context is closing, don't execute any further JavaScript tasks (per section 4.1.1 of the Web Workers spec). However, there may be implementation cleanup tasks in the queue, so keep running through it.
202
203 switch (result) {
204 case MessageQueueTerminated:
205 break;
206
207 case MessageQueueMessageReceived:
208 task->performTask(context);
209 break;
210
211 case MessageQueueTimeout:
212 if (!context->isClosing() && !isBeingDebugged())
213 m_sharedTimer->fire();
214 break;
215 }
216
217#if USE(CF)
218 if (result != MessageQueueTerminated) {
219 if (nextCFRunLoopTimerFireDate <= CFAbsoluteTimeGetCurrent())
220 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, /*returnAfterSourceHandled*/ false);
221 }
222#endif
223
224 return result;
225}
226
227void WorkerRunLoop::runCleanupTasks(WorkerGlobalScope* context)
228{
229 ASSERT(context);
230 ASSERT(context->thread().thread() == &Thread::current());
231 ASSERT(m_messageQueue.killed());
232
233 while (true) {
234 auto task = m_messageQueue.tryGetMessageIgnoringKilled();
235 if (!task)
236 return;
237 task->performTask(context);
238 }
239}
240
241void WorkerRunLoop::terminate()
242{
243 m_messageQueue.kill();
244}
245
246void WorkerRunLoop::postTask(ScriptExecutionContext::Task&& task)
247{
248 postTaskForMode(WTFMove(task), defaultMode());
249}
250
251void WorkerRunLoop::postTaskAndTerminate(ScriptExecutionContext::Task&& task)
252{
253 m_messageQueue.appendAndKill(std::make_unique<Task>(WTFMove(task), defaultMode()));
254}
255
256void WorkerRunLoop::postTaskForMode(ScriptExecutionContext::Task&& task, const String& mode)
257{
258 m_messageQueue.append(std::make_unique<Task>(WTFMove(task), mode));
259}
260
261void WorkerRunLoop::postDebuggerTask(ScriptExecutionContext::Task&& task)
262{
263 postTaskForMode(WTFMove(task), debuggerMode());
264}
265
266void WorkerRunLoop::Task::performTask(WorkerGlobalScope* context)
267{
268 if ((!context->isClosing() && context->script() && !context->script()->isTerminatingExecution()) || m_task.isCleanupTask())
269 m_task.performTask(*context);
270}
271
272WorkerRunLoop::Task::Task(ScriptExecutionContext::Task&& task, const String& mode)
273 : m_task(WTFMove(task))
274 , m_mode(mode.isolatedCopy())
275{
276}
277
278} // namespace WebCore
279