| /* |
| * Copyright (C) 2008 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 COMPUTER, 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 COMPUTER, 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 "core/workers/WorkerThread.h" |
| |
| #include "bindings/core/v8/Microtask.h" |
| #include "bindings/core/v8/ScriptSourceCode.h" |
| #include "bindings/core/v8/V8GCController.h" |
| #include "bindings/core/v8/V8IdleTaskRunner.h" |
| #include "bindings/core/v8/WorkerOrWorkletScriptController.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/inspector/InspectorTaskRunner.h" |
| #include "core/inspector/WorkerThreadDebugger.h" |
| #include "core/origin_trials/OriginTrialContext.h" |
| #include "core/workers/WorkerBackingThread.h" |
| #include "core/workers/WorkerClients.h" |
| #include "core/workers/WorkerGlobalScope.h" |
| #include "core/workers/WorkerReportingProxy.h" |
| #include "core/workers/WorkerThreadStartupData.h" |
| #include "platform/CrossThreadFunctional.h" |
| #include "platform/Histogram.h" |
| #include "platform/WaitableEvent.h" |
| #include "platform/WebThreadSupportingGC.h" |
| #include "platform/heap/SafePoint.h" |
| #include "platform/heap/ThreadState.h" |
| #include "platform/scheduler/CancellableTaskFactory.h" |
| #include "platform/weborigin/KURL.h" |
| #include "public/platform/WebThread.h" |
| #include "wtf/Functional.h" |
| #include "wtf/Noncopyable.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/Threading.h" |
| #include "wtf/text/WTFString.h" |
| #include <limits.h> |
| #include <memory> |
| |
| namespace blink { |
| |
| // TODO(nhiroki): Adjust the delay based on UMA. |
| const long long kForceTerminationDelayInMs = 2000; // 2 secs |
| |
| // ForceTerminationTask is used for posting a delayed task to terminate the |
| // worker execution from the main thread. This task is expected to run when the |
| // shutdown sequence does not start in a certain time period because of an |
| // inifite loop in the JS execution context etc. When the shutdown sequence is |
| // started before this task runs, the task is simply cancelled. |
| class WorkerThread::ForceTerminationTask final { |
| public: |
| static std::unique_ptr<ForceTerminationTask> create(WorkerThread* workerThread) |
| { |
| return wrapUnique(new ForceTerminationTask(workerThread)); |
| } |
| |
| void schedule() |
| { |
| DCHECK(isMainThread()); |
| Platform::current()->mainThread()->getWebTaskRunner()->postDelayedTask(BLINK_FROM_HERE, m_cancellableTaskFactory->cancelAndCreate(), m_workerThread->m_forceTerminationDelayInMs); |
| } |
| |
| private: |
| explicit ForceTerminationTask(WorkerThread* workerThread) |
| : m_workerThread(workerThread) |
| { |
| DCHECK(isMainThread()); |
| m_cancellableTaskFactory = CancellableTaskFactory::create(this, &ForceTerminationTask::run); |
| } |
| |
| void run() |
| { |
| DCHECK(isMainThread()); |
| MutexLocker lock(m_workerThread->m_threadStateMutex); |
| if (m_workerThread->m_readyToShutdown) { |
| // Shutdown sequence is now running. Just return. |
| return; |
| } |
| if (m_workerThread->m_runningDebuggerTask) { |
| // Any debugger task is guaranteed to finish, so we can wait for the |
| // completion. Shutdown sequence will start after that. |
| return; |
| } |
| |
| m_workerThread->forciblyTerminateExecution(); |
| DCHECK_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->m_exitCode); |
| m_workerThread->m_exitCode = WorkerThread::ExitCode::AsyncForciblyTerminated; |
| } |
| |
| WorkerThread* m_workerThread; |
| std::unique_ptr<CancellableTaskFactory> m_cancellableTaskFactory; |
| }; |
| |
| class WorkerThread::WorkerMicrotaskRunner final : public WebThread::TaskObserver { |
| public: |
| explicit WorkerMicrotaskRunner(WorkerThread* workerThread) |
| : m_workerThread(workerThread) |
| { |
| } |
| |
| void willProcessTask() override |
| { |
| // No tasks should get executed after we have closed. |
| DCHECK(!m_workerThread->globalScope() || !m_workerThread->globalScope()->isClosing()); |
| } |
| |
| void didProcessTask() override |
| { |
| Microtask::performCheckpoint(m_workerThread->isolate()); |
| if (WorkerOrWorkletGlobalScope* global = m_workerThread->globalScope()) { |
| if (WorkerOrWorkletScriptController* scriptController = global->scriptController()) |
| scriptController->getRejectedPromises()->processQueue(); |
| if (global->isClosing()) { |
| // |m_workerThread| will eventually be requested to terminate. |
| m_workerThread->workerReportingProxy().workerGlobalScopeClosed(); |
| |
| // Stop further worker tasks to run after this point. |
| m_workerThread->prepareForShutdownOnWorkerThread(); |
| } |
| } |
| } |
| |
| private: |
| // Thread owns the microtask runner; reference remains |
| // valid for the lifetime of this object. |
| WorkerThread* m_workerThread; |
| }; |
| |
| static Mutex& threadSetMutex() |
| { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, mutex, new Mutex); |
| return mutex; |
| } |
| |
| static HashSet<WorkerThread*>& workerThreads() |
| { |
| DEFINE_STATIC_LOCAL(HashSet<WorkerThread*>, threads, ()); |
| return threads; |
| } |
| |
| WorkerThreadLifecycleContext::WorkerThreadLifecycleContext() |
| { |
| DCHECK(isMainThread()); |
| } |
| |
| WorkerThreadLifecycleContext::~WorkerThreadLifecycleContext() |
| { |
| DCHECK(isMainThread()); |
| } |
| |
| void WorkerThreadLifecycleContext::notifyContextDestroyed() |
| { |
| DCHECK(isMainThread()); |
| DCHECK(!m_wasContextDestroyed); |
| m_wasContextDestroyed = true; |
| LifecycleNotifier::notifyContextDestroyed(); |
| } |
| |
| WorkerThread::~WorkerThread() |
| { |
| DCHECK(isMainThread()); |
| MutexLocker lock(threadSetMutex()); |
| DCHECK(workerThreads().contains(this)); |
| workerThreads().remove(this); |
| |
| DCHECK_NE(ExitCode::NotTerminated, m_exitCode); |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, exitCodeHistogram, new EnumerationHistogram("WorkerThread.ExitCode", static_cast<int>(ExitCode::LastEnum))); |
| exitCodeHistogram.count(static_cast<int>(m_exitCode)); |
| } |
| |
| void WorkerThread::start(std::unique_ptr<WorkerThreadStartupData> startupData) |
| { |
| DCHECK(isMainThread()); |
| |
| if (m_started) |
| return; |
| |
| m_started = true; |
| workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, crossThreadBind(&WorkerThread::initializeOnWorkerThread, crossThreadUnretained(this), passed(std::move(startupData)))); |
| } |
| |
| void WorkerThread::terminate() |
| { |
| DCHECK(isMainThread()); |
| terminateInternal(TerminationMode::Graceful); |
| } |
| |
| void WorkerThread::terminateAndWait() |
| { |
| DCHECK(isMainThread()); |
| |
| // The main thread will be blocked, so asynchronous graceful shutdown does |
| // not work. |
| terminateInternal(TerminationMode::Forcible); |
| m_shutdownEvent->wait(); |
| } |
| |
| void WorkerThread::terminateAndWaitForAllWorkers() |
| { |
| DCHECK(isMainThread()); |
| |
| // Keep this lock to prevent WorkerThread instances from being destroyed. |
| MutexLocker lock(threadSetMutex()); |
| HashSet<WorkerThread*> threads = workerThreads(); |
| |
| // The main thread will be blocked, so asynchronous graceful shutdown does |
| // not work. |
| for (WorkerThread* thread : threads) |
| thread->terminateInternal(TerminationMode::Forcible); |
| |
| for (WorkerThread* thread : threads) |
| thread->m_shutdownEvent->wait(); |
| } |
| |
| v8::Isolate* WorkerThread::isolate() |
| { |
| return workerBackingThread().isolate(); |
| } |
| |
| bool WorkerThread::isCurrentThread() |
| { |
| return workerBackingThread().backingThread().isCurrentThread(); |
| } |
| |
| void WorkerThread::postTask(const WebTraceLocation& location, std::unique_ptr<ExecutionContextTask> task, bool isInstrumented) |
| { |
| if (isInShutdown()) |
| return; |
| if (isInstrumented) { |
| DCHECK(isCurrentThread()); |
| InspectorInstrumentation::asyncTaskScheduled(globalScope(), "Worker task", task.get()); |
| } |
| workerBackingThread().backingThread().postTask(location, crossThreadBind(&WorkerThread::performTaskOnWorkerThread, crossThreadUnretained(this), passed(std::move(task)), isInstrumented)); |
| } |
| |
| void WorkerThread::appendDebuggerTask(std::unique_ptr<CrossThreadClosure> task) |
| { |
| DCHECK(isMainThread()); |
| if (isInShutdown()) |
| return; |
| m_inspectorTaskRunner->appendTask(crossThreadBind(&WorkerThread::performDebuggerTaskOnWorkerThread, crossThreadUnretained(this), passed(std::move(task)))); |
| { |
| MutexLocker lock(m_threadStateMutex); |
| if (isolate() && !m_readyToShutdown) |
| m_inspectorTaskRunner->interruptAndRunAllTasksDontWait(isolate()); |
| } |
| workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, crossThreadBind(&WorkerThread::performDebuggerTaskDontWaitOnWorkerThread, crossThreadUnretained(this))); |
| } |
| |
| void WorkerThread::startRunningDebuggerTasksOnPauseOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| m_pausedInDebugger = true; |
| ThreadDebugger::idleStarted(isolate()); |
| std::unique_ptr<CrossThreadClosure> task; |
| do { |
| { |
| SafePointScope safePointScope(BlinkGC::HeapPointersOnStack); |
| task = m_inspectorTaskRunner->takeNextTask(InspectorTaskRunner::WaitForTask); |
| } |
| if (task) |
| (*task)(); |
| // Keep waiting until execution is resumed. |
| } while (task && m_pausedInDebugger); |
| ThreadDebugger::idleFinished(isolate()); |
| } |
| |
| void WorkerThread::stopRunningDebuggerTasksOnPauseOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| m_pausedInDebugger = false; |
| } |
| |
| WorkerOrWorkletGlobalScope* WorkerThread::globalScope() |
| { |
| DCHECK(isCurrentThread()); |
| return m_globalScope.get(); |
| } |
| |
| bool WorkerThread::terminated() |
| { |
| MutexLocker lock(m_threadStateMutex); |
| return m_terminated; |
| } |
| |
| unsigned WorkerThread::workerThreadCount() |
| { |
| MutexLocker lock(threadSetMutex()); |
| return workerThreads().size(); |
| } |
| |
| PlatformThreadId WorkerThread::platformThreadId() |
| { |
| if (!m_started) |
| return 0; |
| return workerBackingThread().backingThread().platformThread().threadId(); |
| } |
| |
| WorkerThread::WorkerThread(PassRefPtr<WorkerLoaderProxy> workerLoaderProxy, WorkerReportingProxy& workerReportingProxy) |
| : m_forceTerminationDelayInMs(kForceTerminationDelayInMs) |
| , m_inspectorTaskRunner(wrapUnique(new InspectorTaskRunner())) |
| , m_workerLoaderProxy(workerLoaderProxy) |
| , m_workerReportingProxy(workerReportingProxy) |
| , m_terminationEvent(wrapUnique(new WaitableEvent( |
| WaitableEvent::ResetPolicy::Manual, |
| WaitableEvent::InitialState::NonSignaled))) |
| , m_shutdownEvent(wrapUnique(new WaitableEvent( |
| WaitableEvent::ResetPolicy::Manual, |
| WaitableEvent::InitialState::NonSignaled))) |
| , m_workerThreadLifecycleContext(new WorkerThreadLifecycleContext) |
| { |
| DCHECK(isMainThread()); |
| MutexLocker lock(threadSetMutex()); |
| workerThreads().add(this); |
| } |
| |
| void WorkerThread::terminateInternal(TerminationMode mode) |
| { |
| DCHECK(isMainThread()); |
| DCHECK(m_started); |
| bool hasBeenInitialized = true; |
| |
| { |
| // Prevent the deadlock between GC and an attempt to terminate a thread. |
| SafePointScope safePointScope(BlinkGC::HeapPointersOnStack); |
| |
| // Protect against this method, initializeOnWorkerThread() or |
| // termination via the global scope racing each other. |
| MutexLocker lock(m_threadStateMutex); |
| |
| hasBeenInitialized = m_globalScope; |
| |
| // If terminate has already been called. |
| if (m_terminated) { |
| if (m_runningDebuggerTask) { |
| // Any debugger task is guaranteed to finish, so we can wait |
| // for the completion even if the synchronous forcible |
| // termination is requested. Shutdown sequence will start |
| // after the task. |
| DCHECK(!m_scheduledForceTerminationTask); |
| return; |
| } |
| |
| // The synchronous forcible termination request should overtake the |
| // scheduled termination task because the request will block the |
| // main thread and the scheduled termination task never runs. |
| if (mode == TerminationMode::Forcible && m_exitCode == ExitCode::NotTerminated) { |
| DCHECK(m_scheduledForceTerminationTask); |
| m_scheduledForceTerminationTask.reset(); |
| forciblyTerminateExecution(); |
| DCHECK_EQ(ExitCode::NotTerminated, m_exitCode); |
| m_exitCode = ExitCode::SyncForciblyTerminated; |
| } |
| return; |
| } |
| m_terminated = true; |
| |
| // Signal the thread to notify that the thread's stopping. |
| if (m_terminationEvent) |
| m_terminationEvent->signal(); |
| |
| if (!hasBeenInitialized) { |
| // If the worker thread was never initialized, don't start another |
| // shutdown, but still wait for the thread to signal when shutdown |
| // has completed on initializeOnWorkerThread(). |
| DCHECK_EQ(ExitCode::NotTerminated, m_exitCode); |
| m_exitCode = ExitCode::GracefullyTerminated; |
| } else { |
| // Determine if we should synchronously terminate or schedule to |
| // terminate the worker execution so that the task can be handled |
| // by thread event loop. If script execution weren't forbidden, |
| // a while(1) loop in JS could keep the thread alive forever. |
| // |
| // (1) |m_readyToShutdown|: If this is set, the worker thread has |
| // already noticed that the thread is about to be terminated and |
| // the worker global scope is already disposed, so we don't have to |
| // explicitly terminate the worker execution. |
| // |
| // (2) |m_runningDebuggerTask|: Terminating during debugger task |
| // may lead to crash due to heavy use of v8 api in debugger. Any |
| // debugger task is guaranteed to finish, so we can wait for the |
| // completion. |
| bool shouldScheduleToTerminateExecution = !m_readyToShutdown && !m_runningDebuggerTask; |
| |
| if (shouldScheduleToTerminateExecution) { |
| if (mode == TerminationMode::Forcible) { |
| forciblyTerminateExecution(); |
| DCHECK_EQ(ExitCode::NotTerminated, m_exitCode); |
| m_exitCode = ExitCode::SyncForciblyTerminated; |
| } else { |
| DCHECK_EQ(TerminationMode::Graceful, mode); |
| DCHECK(!m_scheduledForceTerminationTask); |
| m_scheduledForceTerminationTask = ForceTerminationTask::create(this); |
| m_scheduledForceTerminationTask->schedule(); |
| } |
| } |
| } |
| } |
| |
| m_workerThreadLifecycleContext->notifyContextDestroyed(); |
| if (!hasBeenInitialized) |
| return; |
| |
| m_inspectorTaskRunner->kill(); |
| workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, crossThreadBind(&WorkerThread::prepareForShutdownOnWorkerThread, crossThreadUnretained(this))); |
| workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, crossThreadBind(&WorkerThread::performShutdownOnWorkerThread, crossThreadUnretained(this))); |
| } |
| |
| void WorkerThread::forciblyTerminateExecution() |
| { |
| DCHECK(m_globalScope); |
| m_globalScope->scriptController()->willScheduleExecutionTermination(); |
| isolate()->TerminateExecution(); |
| } |
| |
| bool WorkerThread::isInShutdown() |
| { |
| // Check if we've started termination or shutdown sequence. Avoid acquiring |
| // a lock here to avoid introducing a risk of deadlock. Note that accessing |
| // |m_terminated| on the main thread or |m_readyToShutdown| on the worker |
| // thread is safe as the flag is set only on the thread. |
| if (isMainThread() && m_terminated) |
| return true; |
| if (isCurrentThread() && m_readyToShutdown) |
| return true; |
| return false; |
| } |
| |
| void WorkerThread::initializeOnWorkerThread(std::unique_ptr<WorkerThreadStartupData> startupData) |
| { |
| DCHECK(isCurrentThread()); |
| KURL scriptURL = startupData->m_scriptURL; |
| String sourceCode = startupData->m_sourceCode; |
| WorkerThreadStartMode startMode = startupData->m_startMode; |
| std::unique_ptr<Vector<char>> cachedMetaData = std::move(startupData->m_cachedMetaData); |
| V8CacheOptions v8CacheOptions = startupData->m_v8CacheOptions; |
| |
| { |
| MutexLocker lock(m_threadStateMutex); |
| |
| // The worker was terminated before the thread had a chance to run. |
| if (m_terminated) { |
| DCHECK_EQ(ExitCode::GracefullyTerminated, m_exitCode); |
| |
| // Notify the proxy that the WorkerOrWorkletGlobalScope has been |
| // disposed of. This can free this thread object, hence it must not |
| // be touched afterwards. |
| m_workerReportingProxy.workerThreadTerminated(); |
| |
| // Notify the main thread that it is safe to deallocate our |
| // resources. |
| m_shutdownEvent->signal(); |
| return; |
| } |
| |
| if (isOwningBackingThread()) |
| workerBackingThread().initialize(); |
| |
| if (shouldAttachThreadDebugger()) |
| V8PerIsolateData::from(isolate())->setThreadDebugger(wrapUnique(new WorkerThreadDebugger(this, isolate()))); |
| m_microtaskRunner = wrapUnique(new WorkerMicrotaskRunner(this)); |
| workerBackingThread().backingThread().addTaskObserver(m_microtaskRunner.get()); |
| |
| // Optimize for memory usage instead of latency for the worker isolate. |
| isolate()->IsolateInBackgroundNotification(); |
| m_globalScope = createWorkerGlobalScope(std::move(startupData)); |
| if (m_globalScope->isWorkerGlobalScope()) |
| toWorkerGlobalScope(m_globalScope)->scriptLoaded(sourceCode.length(), cachedMetaData.get() ? cachedMetaData->size() : 0); |
| |
| // Notify proxy that a new WorkerOrWorkletGlobalScope has been created |
| // and started. |
| m_workerReportingProxy.workerGlobalScopeStarted(m_globalScope.get()); |
| |
| WorkerOrWorkletScriptController* scriptController = m_globalScope->scriptController(); |
| if (!scriptController->isExecutionForbidden()) { |
| scriptController->initializeContextIfNeeded(); |
| |
| // If Origin Trials have been registered before the V8 context was ready, |
| // then inject them into the context now |
| ExecutionContext* executionContext = m_globalScope; |
| if (executionContext) { |
| OriginTrialContext* originTrialContext = OriginTrialContext::from(executionContext); |
| if (originTrialContext) |
| originTrialContext->initializePendingFeatures(); |
| } |
| } |
| } |
| |
| if (startMode == PauseWorkerGlobalScopeOnStart) { |
| startRunningDebuggerTasksOnPauseOnWorkerThread(); |
| |
| // WorkerThread may be ready to shut down at this point if termination |
| // is requested while the debugger task is running. Shutdown sequence |
| // will start soon. |
| if (m_readyToShutdown) |
| return; |
| } |
| |
| if (m_globalScope->scriptController()->isContextInitialized()) { |
| m_workerReportingProxy.didInitializeWorkerContext(); |
| v8::HandleScope handleScope(isolate()); |
| Platform::current()->workerContextCreated(m_globalScope->scriptController()->context()); |
| } |
| |
| if (m_globalScope->isWorkerGlobalScope()) { |
| WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(m_globalScope); |
| CachedMetadataHandler* handler = workerGlobalScope->createWorkerScriptCachedMetadataHandler(scriptURL, cachedMetaData.get()); |
| bool success = workerGlobalScope->scriptController()->evaluate(ScriptSourceCode(sourceCode, scriptURL), nullptr, handler, v8CacheOptions); |
| workerGlobalScope->didEvaluateWorkerScript(); |
| m_workerReportingProxy.didEvaluateWorkerScript(success); |
| } |
| |
| postInitialize(); |
| } |
| |
| void WorkerThread::prepareForShutdownOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| { |
| MutexLocker lock(m_threadStateMutex); |
| if (m_readyToShutdown) |
| return; |
| m_readyToShutdown = true; |
| if (m_exitCode == ExitCode::NotTerminated) |
| m_exitCode = ExitCode::GracefullyTerminated; |
| } |
| |
| m_inspectorTaskRunner->kill(); |
| workerReportingProxy().willDestroyWorkerGlobalScope(); |
| InspectorInstrumentation::allAsyncTasksCanceled(globalScope()); |
| globalScope()->dispose(); |
| workerBackingThread().backingThread().removeTaskObserver(m_microtaskRunner.get()); |
| } |
| |
| void WorkerThread::performShutdownOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| #if DCHECK_IS_ON |
| { |
| MutexLocker lock(m_threadStateMutex); |
| DCHECK(m_terminated); |
| DCHECK(m_readyToShutdown); |
| } |
| #endif |
| |
| // 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. If Oilpan |
| // is enabled, we detach of the context/global scope, with the final heap |
| // cleanup below sweeping it out. |
| m_globalScope->notifyContextDestroyed(); |
| m_globalScope = nullptr; |
| |
| if (isOwningBackingThread()) |
| workerBackingThread().shutdown(); |
| // We must not touch workerBackingThread() from now on. |
| |
| m_microtaskRunner = nullptr; |
| |
| // Notify the proxy that the WorkerOrWorkletGlobalScope has been disposed |
| // of. This can free this thread object, hence it must not be touched |
| // afterwards. |
| workerReportingProxy().workerThreadTerminated(); |
| |
| m_shutdownEvent->signal(); |
| } |
| |
| void WorkerThread::performTaskOnWorkerThread(std::unique_ptr<ExecutionContextTask> task, bool isInstrumented) |
| { |
| DCHECK(isCurrentThread()); |
| if (isInShutdown()) |
| return; |
| |
| WorkerOrWorkletGlobalScope* global = globalScope(); |
| // If the thread is terminated before it had a chance initialize (see |
| // WorkerThread::Initialize()), we mustn't run any of the posted tasks. |
| if (!global) { |
| DCHECK(terminated()); |
| return; |
| } |
| |
| InspectorInstrumentation::AsyncTask asyncTask(global, task.get(), isInstrumented); |
| { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram, scopedUsCounter, new CustomCountHistogram("WorkerThread.Task.Time", 0, 10000000, 50)); |
| ScopedUsHistogramTimer timer(scopedUsCounter); |
| task->performTask(global); |
| } |
| } |
| |
| void WorkerThread::performDebuggerTaskOnWorkerThread(std::unique_ptr<CrossThreadClosure> task) |
| { |
| DCHECK(isCurrentThread()); |
| InspectorTaskRunner::IgnoreInterruptsScope scope(m_inspectorTaskRunner.get()); |
| { |
| MutexLocker lock(m_threadStateMutex); |
| DCHECK(!m_readyToShutdown); |
| m_runningDebuggerTask = true; |
| } |
| ThreadDebugger::idleFinished(isolate()); |
| { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram, scopedUsCounter, new CustomCountHistogram("WorkerThread.DebuggerTask.Time", 0, 10000000, 50)); |
| ScopedUsHistogramTimer timer(scopedUsCounter); |
| (*task)(); |
| } |
| ThreadDebugger::idleStarted(isolate()); |
| { |
| MutexLocker lock(m_threadStateMutex); |
| if (!m_terminated) { |
| m_runningDebuggerTask = false; |
| return; |
| } |
| // terminate() was called. Shutdown sequence will start soon. |
| // Keep |m_runningDebuggerTask| to prevent forcible termination from the |
| // main thread before shutdown preparation. |
| } |
| // Stop further worker tasks to run after this point. |
| prepareForShutdownOnWorkerThread(); |
| } |
| |
| void WorkerThread::performDebuggerTaskDontWaitOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| std::unique_ptr<CrossThreadClosure> task = m_inspectorTaskRunner->takeNextTask(InspectorTaskRunner::DontWaitForTask); |
| if (task) |
| (*task)(); |
| } |
| |
| WorkerThread::ExitCode WorkerThread::getExitCode() |
| { |
| MutexLocker lock(m_threadStateMutex); |
| return m_exitCode; |
| } |
| |
| } // namespace blink |