blob: a3bddbb4c93a9c3140ed6eaaa34dd0b424c4459c [file] [log] [blame]
/*
* Copyright (C) 2008-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WorkerOrWorkletThread.h"
#include "ThreadGlobalData.h"
#include "WorkerEventLoop.h"
#include "WorkerOrWorkletGlobalScope.h"
#include "WorkerOrWorkletScriptController.h"
#if PLATFORM(IOS_FAMILY)
#include "FloatingPointEnvironment.h"
#endif
#if USE(GLIB)
#include <wtf/glib/GRefPtr.h>
#endif
namespace WebCore {
Lock WorkerOrWorkletThread::s_workerOrWorkletThreadsLock;
Lock& WorkerOrWorkletThread::workerOrWorkletThreadsLock()
{
return s_workerOrWorkletThreadsLock;
}
HashSet<WorkerOrWorkletThread*>& WorkerOrWorkletThread::workerOrWorkletThreads()
{
ASSERT(workerOrWorkletThreadsLock().isHeld());
static NeverDestroyed<HashSet<WorkerOrWorkletThread*>> workerOrWorkletThreads;
return workerOrWorkletThreads;
}
static UniqueRef<WorkerRunLoop> constructRunLoop(WorkerThreadMode workerThreadMode)
{
switch (workerThreadMode) {
case WorkerThreadMode::UseMainThread:
return makeUniqueRef<WorkerMainRunLoop>();
case WorkerThreadMode::CreateNewThread:
break;
}
return makeUniqueRef<WorkerDedicatedRunLoop>();
}
WorkerOrWorkletThread::WorkerOrWorkletThread(const String& identifier, WorkerThreadMode workerThreadMode)
: m_identifier(identifier)
, m_runLoop(constructRunLoop(workerThreadMode))
{
Locker locker { workerOrWorkletThreadsLock() };
workerOrWorkletThreads().add(this);
}
WorkerOrWorkletThread::~WorkerOrWorkletThread()
{
Locker locker { workerOrWorkletThreadsLock() };
ASSERT(workerOrWorkletThreads().contains(this));
workerOrWorkletThreads().remove(this);
}
void WorkerOrWorkletThread::startRunningDebuggerTasks()
{
ASSERT(!m_pausedForDebugger);
m_pausedForDebugger = true;
// FIXME: Add support for debugging workers running on the main thread.
if (!is<WorkerDedicatedRunLoop>(m_runLoop.get()))
return;
MessageQueueWaitResult result;
do {
result = downcast<WorkerDedicatedRunLoop>(m_runLoop.get()).runInDebuggerMode(*m_globalScope);
} while (result != MessageQueueTerminated && m_pausedForDebugger);
}
void WorkerOrWorkletThread::stopRunningDebuggerTasks()
{
m_pausedForDebugger = false;
}
void WorkerOrWorkletThread::runEventLoop()
{
// Does not return until terminated.
if (is<WorkerDedicatedRunLoop>(m_runLoop.get()))
downcast<WorkerDedicatedRunLoop>(m_runLoop.get()).run(m_globalScope.get());
}
void WorkerOrWorkletThread::workerOrWorkletThread()
{
Ref protectedThis { *this };
if (isMainThread()) {
m_globalScope = createGlobalScope();
if (!m_globalScope)
return;
downcast<WorkerMainRunLoop>(m_runLoop.get()).setGlobalScope(*m_globalScope);
String exceptionMessage;
evaluateScriptIfNecessary(exceptionMessage);
callOnMainThread([evaluateCallback = WTFMove(m_evaluateCallback), message = WTFMove(exceptionMessage)] {
if (evaluateCallback)
evaluateCallback(message);
});
return;
}
// Propagate the mainThread's fenv to workers.
#if PLATFORM(IOS_FAMILY)
FloatingPointEnvironment::singleton().propagateMainThreadEnvironment();
#endif
#if USE(GLIB)
GRefPtr<GMainContext> mainContext = adoptGRef(g_main_context_new());
g_main_context_push_thread_default(mainContext.get());
#endif
WorkerOrWorkletScriptController* scriptController;
{
// Mutex protection is necessary to ensure that we don't change m_globalScope
// while WorkerThread::stop() is accessing it. Note that WorkerThread::stop() can
// be called before we've finished creating the WorkerGlobalScope.
Locker locker { m_threadCreationAndGlobalScopeLock };
m_globalScope = createGlobalScope();
// When running out of memory, createGlobalScope() may return null because we could not allocate a JSC::VM.
if (!m_globalScope) {
WTFLogAlways("Error: Failed to create a WorkerOrWorkerGlobalScope.");
return;
}
scriptController = m_globalScope->script();
if (m_runLoop->terminated()) {
// The worker was terminated before the thread had a chance to run. Since the context didn't exist yet,
// forbidExecution() couldn't be called from stop().
scriptController->scheduleExecutionTermination();
scriptController->forbidExecution();
}
}
if (shouldWaitForWebInspectorOnStartup()) {
startRunningDebuggerTasks();
// If the worker was somehow terminated while processing debugger commands.
if (m_runLoop->terminated())
scriptController->forbidExecution();
}
String exceptionMessage;
evaluateScriptIfNecessary(exceptionMessage);
callOnMainThread([evaluateCallback = WTFMove(m_evaluateCallback), message = exceptionMessage.isolatedCopy()] {
if (evaluateCallback)
evaluateCallback(message);
});
runEventLoop();
#if USE(GLIB)
g_main_context_pop_thread_default(mainContext.get());
#endif
RefPtr<Thread> protector = m_thread;
ASSERT(m_globalScope->hasOneRef());
RefPtr<WorkerOrWorkletGlobalScope> workerGlobalScopeToDelete;
{
// Mutex protection is necessary to ensure that we don't change m_globalScope
// while WorkerThread::stop is accessing it.
Locker locker { m_threadCreationAndGlobalScopeLock };
// Delay the destruction of the WorkerGlobalScope context until after we've unlocked the
// m_threadCreationAndWorkerGlobalScopeMutex. This is needed because destructing the
// context will trigger the main thread to race against us to delete the WorkerThread
// object, and the WorkerThread object owns the mutex we need to unlock after this.
workerGlobalScopeToDelete = std::exchange(m_globalScope, nullptr);
if (m_stoppedCallback)
callOnMainThread(WTFMove(m_stoppedCallback));
}
// The below assignment will destroy the context, which will in turn notify messaging proxy.
// We cannot let any objects survive past thread exit, because no other thread will run GC or otherwise destroy them.
workerGlobalScopeToDelete = nullptr;
// Clean up WebCore::ThreadGlobalData before WTF::Thread goes away!
threadGlobalData().destroy();
// Send the last WorkerThread Ref to be Deref'ed on the main thread.
callOnMainThread([protectedThis = WTFMove(protectedThis)] { });
// The thread object may be already destroyed from notification now, don't try to access "this".
protector->detach();
}
void WorkerOrWorkletThread::start(WTF::Function<void(const String&)>&& evaluateCallback)
{
// Mutex protection is necessary to ensure that m_thread is initialized when the thread starts.
Locker locker { m_threadCreationAndGlobalScopeLock };
if (m_thread)
return;
m_evaluateCallback = WTFMove(evaluateCallback);
auto thread = createThread();
// Force the Thread object to be initialized fully before storing it to m_thread (and becoming visible to other threads).
WTF::storeStoreFence();
m_thread = WTFMove(thread);
}
void WorkerOrWorkletThread::stop(Function<void()>&& stoppedCallback)
{
// Mutex protection is necessary to ensure that m_workerGlobalScope isn't changed by
// WorkerThread::workerThread() while we're accessing it. Note also that stop() can
// be called before m_workerGlobalScope is fully created.
if (!m_threadCreationAndGlobalScopeLock.tryLock()) {
// The thread is still starting, spin the runloop and try again to avoid deadlocks if the worker thread
// needs to interact with the main thread during startup.
callOnMainThread([this, stoppedCallback = WTFMove(stoppedCallback)]() mutable {
stop(WTFMove(stoppedCallback));
});
return;
}
Locker locker { AdoptLock, m_threadCreationAndGlobalScopeLock };
// If the thread is suspended, resume it now so that we can dispatch the cleanup tasks below.
if (m_isSuspended)
resume();
ASSERT(!m_stoppedCallback);
m_stoppedCallback = WTFMove(stoppedCallback);
// 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.
if (globalScope()) {
globalScope()->script()->scheduleExecutionTermination();
if (is<WorkerMainRunLoop>(m_runLoop.get())) {
auto globalScope = std::exchange(m_globalScope, nullptr);
globalScope->prepareForDestruction();
globalScope->clearScript();
m_runLoop->terminate();
if (m_stoppedCallback)
callOnMainThread(std::exchange(m_stoppedCallback, nullptr));
return;
}
m_runLoop->postTaskAndTerminate({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context ) {
auto& globalScope = downcast<WorkerOrWorkletGlobalScope>(context);
globalScope.prepareForDestruction();
// Stick a shutdown command at the end of the queue, so that we deal
// with all the cleanup tasks the databases post first.
globalScope.postTask({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context) {
auto& globalScope = downcast<WorkerOrWorkletGlobalScope>(context);
// It's not safe to call clearScript until all the cleanup tasks posted by functions initiated by WorkerThreadShutdownStartTask have completed.
globalScope.clearScript();
} });
} });
return;
}
m_runLoop->terminate();
}
void WorkerOrWorkletThread::suspend()
{
m_isSuspended = true;
if (is<WorkerMainRunLoop>(m_runLoop.get()))
return;
m_runLoop->postTask([&](ScriptExecutionContext&) {
if (globalScope())
globalScope()->suspend();
m_suspensionSemaphore.wait();
if (globalScope())
globalScope()->resume();
});
}
void WorkerOrWorkletThread::resume()
{
ASSERT(m_isSuspended);
m_isSuspended = false;
if (is<WorkerMainRunLoop>(m_runLoop.get()))
return;
m_suspensionSemaphore.signal();
}
void WorkerOrWorkletThread::releaseFastMallocFreeMemoryInAllThreads()
{
Locker locker { workerOrWorkletThreadsLock() };
for (auto* workerOrWorkletThread : workerOrWorkletThreads()) {
workerOrWorkletThread->runLoop().postTask([] (ScriptExecutionContext&) {
WTF::releaseFastMallocFreeMemory();
});
}
}
} // namespace WebCore