1/*
2 * Copyright (C) 2008 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
27#include "config.h"
28
29#include "WorkerThread.h"
30
31#include "DedicatedWorkerGlobalScope.h"
32#include "InspectorInstrumentation.h"
33#include "ScriptSourceCode.h"
34#include "SecurityOrigin.h"
35#include "ThreadGlobalData.h"
36#include "URL.h"
37#include <utility>
38#include <wtf/NeverDestroyed.h>
39#include <wtf/Noncopyable.h>
40#include <wtf/text/WTFString.h>
41
42#if ENABLE(SQL_DATABASE)
43#include "DatabaseManager.h"
44#include "DatabaseTask.h"
45#endif
46
47#if PLATFORM(IOS)
48#include "FloatingPointEnvironment.h"
49#include "WebCoreThread.h"
50#endif
51
52namespace WebCore {
53
54static std::mutex& threadSetMutex()
55{
56    static std::once_flag onceFlag;
57    static LazyNeverDestroyed<std::mutex> mutex;
58
59    std::call_once(onceFlag, []{
60        mutex.construct();
61    });
62
63    return mutex;
64}
65
66static HashSet<WorkerThread*>& workerThreads()
67{
68    static NeverDestroyed<HashSet<WorkerThread*>> workerThreads;
69
70    return workerThreads;
71}
72
73unsigned WorkerThread::workerThreadCount()
74{
75    std::lock_guard<std::mutex> lock(threadSetMutex());
76
77    return workerThreads().size();
78}
79
80struct WorkerThreadStartupData {
81    WTF_MAKE_NONCOPYABLE(WorkerThreadStartupData); WTF_MAKE_FAST_ALLOCATED;
82public:
83    WorkerThreadStartupData(const URL& scriptURL, const String& userAgent, const GroupSettings*, const String& sourceCode, WorkerThreadStartMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin);
84
85    URL m_scriptURL;
86    String m_userAgent;
87    std::unique_ptr<GroupSettings> m_groupSettings;
88    String m_sourceCode;
89    WorkerThreadStartMode m_startMode;
90    String m_contentSecurityPolicy;
91    ContentSecurityPolicy::HeaderType m_contentSecurityPolicyType;
92    RefPtr<SecurityOrigin> m_topOrigin;
93};
94
95WorkerThreadStartupData::WorkerThreadStartupData(const URL& scriptURL, const String& userAgent, const GroupSettings* settings, const String& sourceCode, WorkerThreadStartMode startMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin)
96    : m_scriptURL(scriptURL.copy())
97    , m_userAgent(userAgent.isolatedCopy())
98    , m_sourceCode(sourceCode.isolatedCopy())
99    , m_startMode(startMode)
100    , m_contentSecurityPolicy(contentSecurityPolicy.isolatedCopy())
101    , m_contentSecurityPolicyType(contentSecurityPolicyType)
102    , m_topOrigin(topOrigin ? topOrigin->isolatedCopy() : 0)
103{
104    if (!settings)
105        return;
106
107    m_groupSettings = std::make_unique<GroupSettings>();
108    m_groupSettings->setLocalStorageQuotaBytes(settings->localStorageQuotaBytes());
109    m_groupSettings->setIndexedDBQuotaBytes(settings->indexedDBQuotaBytes());
110    m_groupSettings->setIndexedDBDatabasePath(settings->indexedDBDatabasePath().isolatedCopy());
111}
112
113WorkerThread::WorkerThread(const URL& scriptURL, const String& userAgent, const GroupSettings* settings, const String& sourceCode, WorkerLoaderProxy& workerLoaderProxy, WorkerReportingProxy& workerReportingProxy, WorkerThreadStartMode startMode, const String& contentSecurityPolicy, ContentSecurityPolicy::HeaderType contentSecurityPolicyType, const SecurityOrigin* topOrigin)
114    : m_threadID(0)
115    , m_workerLoaderProxy(workerLoaderProxy)
116    , m_workerReportingProxy(workerReportingProxy)
117    , m_startupData(std::make_unique<WorkerThreadStartupData>(scriptURL, userAgent, settings, sourceCode, startMode, contentSecurityPolicy, contentSecurityPolicyType, topOrigin))
118#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
119    , m_notificationClient(0)
120#endif
121{
122    std::lock_guard<std::mutex> lock(threadSetMutex());
123
124    workerThreads().add(this);
125}
126
127WorkerThread::~WorkerThread()
128{
129    std::lock_guard<std::mutex> lock(threadSetMutex());
130
131    ASSERT(workerThreads().contains(this));
132    workerThreads().remove(this);
133}
134
135bool WorkerThread::start()
136{
137    // Mutex protection is necessary to ensure that m_threadID is initialized when the thread starts.
138    MutexLocker lock(m_threadCreationMutex);
139
140    if (m_threadID)
141        return true;
142
143    m_threadID = createThread(WorkerThread::workerThreadStart, this, "WebCore: Worker");
144
145    return m_threadID;
146}
147
148void WorkerThread::workerThreadStart(void* thread)
149{
150    static_cast<WorkerThread*>(thread)->workerThread();
151}
152
153void WorkerThread::workerThread()
154{
155    // Propagate the mainThread's fenv to workers.
156#if PLATFORM(IOS)
157    FloatingPointEnvironment::shared().propagateMainThreadEnvironment();
158#endif
159
160    {
161        MutexLocker lock(m_threadCreationMutex);
162        m_workerGlobalScope = createWorkerGlobalScope(m_startupData->m_scriptURL, m_startupData->m_userAgent, WTF::move(m_startupData->m_groupSettings), m_startupData->m_contentSecurityPolicy, m_startupData->m_contentSecurityPolicyType, m_startupData->m_topOrigin.release());
163
164        if (m_runLoop.terminated()) {
165            // The worker was terminated before the thread had a chance to run. Since the context didn't exist yet,
166            // forbidExecution() couldn't be called from stop().
167            m_workerGlobalScope->script()->forbidExecution();
168        }
169    }
170
171    WorkerScriptController* script = m_workerGlobalScope->script();
172#if ENABLE(INSPECTOR)
173    InspectorInstrumentation::willEvaluateWorkerScript(workerGlobalScope(), m_startupData->m_startMode);
174#endif
175    script->evaluate(ScriptSourceCode(m_startupData->m_sourceCode, m_startupData->m_scriptURL));
176    // Free the startup data to cause its member variable deref's happen on the worker's thread (since
177    // all ref/derefs of these objects are happening on the thread at this point). Note that
178    // WorkerThread::~WorkerThread happens on a different thread where it was created.
179    m_startupData = nullptr;
180
181    runEventLoop();
182
183    ThreadIdentifier threadID = m_threadID;
184
185    ASSERT(m_workerGlobalScope->hasOneRef());
186
187    // The below assignment will destroy the context, which will in turn notify messaging proxy.
188    // We cannot let any objects survive past thread exit, because no other thread will run GC or otherwise destroy them.
189    m_workerGlobalScope = 0;
190
191    // Clean up WebCore::ThreadGlobalData before WTF::WTFThreadData goes away!
192    threadGlobalData().destroy();
193
194    // The thread object may be already destroyed from notification now, don't try to access "this".
195    detachThread(threadID);
196}
197
198void WorkerThread::runEventLoop()
199{
200    // Does not return until terminated.
201    m_runLoop.run(m_workerGlobalScope.get());
202}
203
204void WorkerThread::stop()
205{
206    // Mutex protection is necessary because stop() can be called before the context is fully created.
207    MutexLocker lock(m_threadCreationMutex);
208
209    // Ensure that tasks are being handled by thread event loop. If script execution weren't forbidden, a while(1) loop in JS could keep the thread alive forever.
210    if (m_workerGlobalScope) {
211        m_workerGlobalScope->script()->scheduleExecutionTermination();
212
213#if ENABLE(SQL_DATABASE)
214        DatabaseManager::manager().interruptAllDatabasesForContext(m_workerGlobalScope.get());
215#endif
216        m_runLoop.postTaskAndTerminate({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context ) {
217            WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(&context);
218
219#if ENABLE(SQL_DATABASE)
220            // FIXME: Should we stop the databases as part of stopActiveDOMObjects() below?
221            DatabaseTaskSynchronizer cleanupSync;
222            DatabaseManager::manager().stopDatabases(workerGlobalScope, &cleanupSync);
223#endif
224
225            workerGlobalScope->stopActiveDOMObjects();
226
227            workerGlobalScope->notifyObserversOfStop();
228
229            // Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects,
230            // which become dangling once Heap is destroyed.
231            workerGlobalScope->removeAllEventListeners();
232
233#if ENABLE(SQL_DATABASE)
234            // We wait for the database thread to clean up all its stuff so that we
235            // can do more stringent leak checks as we exit.
236            cleanupSync.waitForTaskCompletion();
237#endif
238
239            // Stick a shutdown command at the end of the queue, so that we deal
240            // with all the cleanup tasks the databases post first.
241            workerGlobalScope->postTask({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context) {
242                WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(&context);
243                // It's not safe to call clearScript until all the cleanup tasks posted by functions initiated by WorkerThreadShutdownStartTask have completed.
244                workerGlobalScope->clearScript();
245            } });
246
247        } });
248        return;
249    }
250    m_runLoop.terminate();
251}
252
253void WorkerThread::releaseFastMallocFreeMemoryInAllThreads()
254{
255    std::lock_guard<std::mutex> lock(threadSetMutex());
256
257    for (auto* workerThread : workerThreads())
258        workerThread->runLoop().postTask([] (ScriptExecutionContext&) {
259            WTF::releaseFastMallocFreeMemory();
260        });
261}
262
263} // namespace WebCore
264