282 lines
9.9 KiB
C++
282 lines
9.9 KiB
C++
/*
|
|
* 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 "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;
|
|
}
|
|
|
|
WorkerOrWorkletThread::WorkerOrWorkletThread(const String& identifier)
|
|
: m_identifier(identifier)
|
|
{
|
|
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;
|
|
|
|
MessageQueueWaitResult result;
|
|
do {
|
|
result = m_runLoop.runInDebuggerMode(*m_globalScope);
|
|
} while (result != MessageQueueTerminated && m_pausedForDebugger);
|
|
}
|
|
|
|
void WorkerOrWorkletThread::stopRunningDebuggerTasks()
|
|
{
|
|
m_pausedForDebugger = false;
|
|
}
|
|
|
|
void WorkerOrWorkletThread::runEventLoop()
|
|
{
|
|
// Does not return until terminated.
|
|
m_runLoop.run(m_globalScope.get());
|
|
}
|
|
|
|
void WorkerOrWorkletThread::workerOrWorkletThread()
|
|
{
|
|
auto protectedThis = makeRef(*this);
|
|
|
|
// 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();
|
|
|
|
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;
|
|
m_runLoop.postTask([&](ScriptExecutionContext&) {
|
|
if (globalScope())
|
|
globalScope()->suspend();
|
|
|
|
m_suspensionSemaphore.wait();
|
|
|
|
if (globalScope())
|
|
globalScope()->resume();
|
|
});
|
|
}
|
|
|
|
void WorkerOrWorkletThread::resume()
|
|
{
|
|
ASSERT(m_isSuspended);
|
|
m_isSuspended = false;
|
|
m_suspensionSemaphore.signal();
|
|
}
|
|
|
|
void WorkerOrWorkletThread::releaseFastMallocFreeMemoryInAllThreads()
|
|
{
|
|
Locker locker { workerOrWorkletThreadsLock() };
|
|
for (auto* workerOrWorkletThread : workerOrWorkletThreads()) {
|
|
workerOrWorkletThread->runLoop().postTask([] (ScriptExecutionContext&) {
|
|
WTF::releaseFastMallocFreeMemory();
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace WebCore
|