Index: third_party/WebKit/Source/core/workers/WorkerThread.cpp |
diff --git a/third_party/WebKit/Source/core/workers/WorkerThread.cpp b/third_party/WebKit/Source/core/workers/WorkerThread.cpp |
index 1b521bc58305b8d4b9f7db3e4605f4e817cdf609..860ed8f2cbaeb34aef0011994adadd71e3e2335b 100644 |
--- a/third_party/WebKit/Source/core/workers/WorkerThread.cpp |
+++ b/third_party/WebKit/Source/core/workers/WorkerThread.cpp |
@@ -42,6 +42,7 @@ |
#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" |
@@ -51,7 +52,54 @@ |
namespace blink { |
-class WorkerThread::WorkerMicrotaskRunner : public WebThread::TaskObserver { |
+// 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 PassOwnPtr<ForceTerminationTask> create(WorkerThread* workerThread) |
+ { |
+ return adoptPtr(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; |
+ } |
+ |
+ m_workerThread->forciblyTerminateExecution(); |
+ DCHECK_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->m_exitCode); |
+ m_workerThread->m_exitCode = WorkerThread::ExitCode::AsyncForciblyTerminated; |
+ } |
+ |
+ WorkerThread* m_workerThread; |
+ OwnPtr<CancellableTaskFactory> m_cancellableTaskFactory; |
+}; |
+ |
+class WorkerThread::WorkerMicrotaskRunner final : public WebThread::TaskObserver { |
public: |
explicit WorkerMicrotaskRunner(WorkerThread* workerThread) |
: m_workerThread(workerThread) |
@@ -100,9 +148,14 @@ static HashSet<WorkerThread*>& workerThreads() |
WorkerThread::~WorkerThread() |
{ |
+ DCHECK(isMainThread()); |
MutexLocker lock(threadSetMutex()); |
DCHECK(workerThreads().contains(this)); |
workerThreads().remove(this); |
+ |
+ // TODO(nhiroki): Record how this thread is terminated (i.e. m_exitCode) |
+ // in UMA. |
+ DCHECK_NE(ExitCode::NotTerminated, m_exitCode); |
} |
void WorkerThread::start(PassOwnPtr<WorkerThreadStartupData> startupData) |
@@ -119,66 +172,16 @@ void WorkerThread::start(PassOwnPtr<WorkerThreadStartupData> startupData) |
void WorkerThread::terminate() |
{ |
DCHECK(isMainThread()); |
- |
- // 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); |
- |
- // If terminate has already been called, just return. |
- if (m_terminated) |
- return; |
- m_terminated = true; |
- |
- // Signal the thread to notify that the thread's stopping. |
- if (m_terminationEvent) |
- m_terminationEvent->signal(); |
- |
- // 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(). |
- if (!m_workerGlobalScope) |
- return; |
- |
- // Determine if we should terminate the isolate 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|: It 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 |
- // isolate. |
- // |
- // (2) |workerScriptCount() == 1|: If other WorkerGlobalScopes are running |
- // on the worker thread, we should not terminate the isolate. This condition |
- // is not entirely correct because other scripts can be being initialized or |
- // terminated simuletaneously. Though this function itself is protected by a |
- // mutex, it is possible that |workerScriptCount()| here is not consistent |
- // with that in |initialize| and |shutdown|. |
- // |
- // (3) |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 shouldTerminateIsolate = !m_readyToShutdown && (workerBackingThread().workerScriptCount() == 1) && !m_runningDebuggerTask; |
- |
- if (shouldTerminateIsolate) { |
- // TODO(yhirano): TerminateExecution should be called more |
- // carefully (https://crbug.com/413518). |
- m_workerGlobalScope->scriptController()->willScheduleExecutionTermination(); |
- isolate()->TerminateExecution(); |
- } |
- |
- m_inspectorTaskRunner->kill(); |
- workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBind(&WorkerThread::prepareForShutdownOnWorkerThread, AllowCrossThreadAccess(this))); |
- workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBind(&WorkerThread::performShutdownOnWorkerThread, AllowCrossThreadAccess(this))); |
+ terminateInternal(TerminationMode::Graceful); |
} |
void WorkerThread::terminateAndWait() |
{ |
DCHECK(isMainThread()); |
- terminate(); |
+ |
+ // The main thread will be blocked, so asynchronous graceful shutdown does |
+ // not work. |
+ terminateInternal(TerminationMode::Forcible); |
m_shutdownEvent->wait(); |
} |
@@ -189,8 +192,11 @@ void WorkerThread::terminateAndWaitForAllWorkers() |
// 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->terminate(); |
+ thread->terminateInternal(TerminationMode::Forcible); |
for (WorkerThread* thread : threads) |
thread->m_shutdownEvent->wait(); |
@@ -275,7 +281,8 @@ PlatformThreadId WorkerThread::platformThreadId() |
} |
WorkerThread::WorkerThread(PassRefPtr<WorkerLoaderProxy> workerLoaderProxy, WorkerReportingProxy& workerReportingProxy) |
- : m_inspectorTaskRunner(adoptPtr(new InspectorTaskRunner())) |
+ : m_forceTerminationDelayInMs(kForceTerminationDelayInMs) |
+ , m_inspectorTaskRunner(adoptPtr(new InspectorTaskRunner())) |
, m_workerLoaderProxy(workerLoaderProxy) |
, m_workerReportingProxy(workerReportingProxy) |
, m_terminationEvent(adoptPtr(new WaitableEvent( |
@@ -300,6 +307,93 @@ std::unique_ptr<CrossThreadClosure> WorkerThread::createWorkerThreadTask(std::un |
return threadSafeBind(&WorkerThread::performTaskOnWorkerThread, AllowCrossThreadAccess(this), passed(std::move(task)), isInstrumented); |
} |
+void WorkerThread::terminateInternal(TerminationMode mode) |
+{ |
+ DCHECK(m_started); |
+ |
+ // 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); |
+ |
+ // If terminate has already been called. |
+ if (m_terminated) { |
+ // 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 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(). |
+ if (!m_workerGlobalScope) { |
+ DCHECK_EQ(ExitCode::NotTerminated, m_exitCode); |
+ m_exitCode = ExitCode::GracefullyTerminated; |
+ return; |
+ } |
+ |
+ // 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|: It 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) |workerScriptCount() == 1|: If other WorkerGlobalScopes are running |
+ // on the worker thread, we should not terminate the worker execution. This |
+ // condition is not entirely correct because other scripts can be being |
+ // initialized or terminated simuletaneously. Though this function itself is |
+ // protected by a mutex, it is possible that |workerScriptCount()| here is |
+ // not consistent with that in |initialize| and |shutdown|. |
+ // |
+ // (3) |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 && (workerBackingThread().workerScriptCount() == 1) && !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_inspectorTaskRunner->kill(); |
+ workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBind(&WorkerThread::prepareForShutdownOnWorkerThread, AllowCrossThreadAccess(this))); |
+ workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBind(&WorkerThread::performShutdownOnWorkerThread, AllowCrossThreadAccess(this))); |
+} |
+ |
+void WorkerThread::forciblyTerminateExecution() |
+{ |
+ DCHECK(m_workerGlobalScope); |
+ m_workerGlobalScope->scriptController()->willScheduleExecutionTermination(); |
+ isolate()->TerminateExecution(); |
+} |
+ |
void WorkerThread::initializeOnWorkerThread(PassOwnPtr<WorkerThreadStartupData> startupData) |
{ |
KURL scriptURL = startupData->m_scriptURL; |
@@ -313,6 +407,8 @@ void WorkerThread::initializeOnWorkerThread(PassOwnPtr<WorkerThreadStartupData> |
// 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 WorkerGlobalScope has been disposed of. |
// This can free this thread object, hence it must not be touched |
// afterwards. |
@@ -369,6 +465,8 @@ void WorkerThread::prepareForShutdownOnWorkerThread() |
if (m_readyToShutdown) |
return; |
m_readyToShutdown = true; |
+ if (m_exitCode == ExitCode::NotTerminated) |
+ m_exitCode = ExitCode::GracefullyTerminated; |
} |
workerReportingProxy().willDestroyWorkerGlobalScope(); |
@@ -402,7 +500,8 @@ void WorkerThread::performShutdownOnWorkerThread() |
m_microtaskRunner = nullptr; |
// Notify the proxy that the WorkerGlobalScope has been disposed of. |
- // This can free this thread object, hence it must not be touched afterwards. |
+ // This can free this thread object, hence it must not be touched |
+ // afterwards. |
workerReportingProxy().workerThreadTerminated(); |
m_shutdownEvent->signal(); |
@@ -460,4 +559,10 @@ void WorkerThread::runDebuggerTaskDontWaitOnWorkerThread() |
(*task)(); |
} |
+WorkerThread::ExitCode WorkerThread::getExitCode() |
+{ |
+ MutexLocker lock(m_threadStateMutex); |
+ return m_exitCode; |
+} |
+ |
} // namespace blink |