Chromium Code Reviews| 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..9e6103cac504b9fdd8653bd82f772b8509b93f9c 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,55 @@ |
| namespace blink { |
| -class WorkerThread::WorkerMicrotaskRunner : public WebThread::TaskObserver { |
| +// TODO(nhiroki): Adjust the delay based on UMA. |
| +const long long kForceTerminationDelayInMs = 3000; // 3 secs |
| + |
| +// ForceTerminationTask is used for posting a delayed task to terminate an |
| +// isolate 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->m_workerGlobalScope->scriptController()->willScheduleExecutionTermination(); |
|
yhirano
2016/05/31 04:48:07
[optional] It might be good to make a function for
nhiroki
2016/05/31 06:08:56
Done.
|
| + m_workerThread->isolate()->TerminateExecution(); |
| + 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 +149,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 +173,22 @@ 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); |
| + |
| + // Terminating the worker thread that hasn't started yet does not run the |
| + // shutdown sequence on the worker thread, so should not wait for the |
| + // shutdown event. |
| + if (m_exitCode == ExitCode::TerminatedBeforeStarting) |
| + return; |
| m_shutdownEvent->wait(); |
| } |
| @@ -189,11 +199,20 @@ void WorkerThread::terminateAndWaitForAllWorkers() |
| // Keep this lock to prevent WorkerThread instances from being destroyed. |
| MutexLocker lock(threadSetMutex()); |
| HashSet<WorkerThread*> threads = workerThreads(); |
| - for (WorkerThread* thread : threads) |
| - thread->terminate(); |
| + // 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) { |
| + // Terminating the worker thread that hasn't started yet does not run |
| + // the shutdown sequence on the worker thread, so should not wait for |
| + // the shutdown event. |
| + if (thread->m_exitCode == ExitCode::TerminatedBeforeStarting) |
| + continue; |
| thread->m_shutdownEvent->wait(); |
| + } |
| } |
| v8::Isolate* WorkerThread::isolate() |
| @@ -275,7 +294,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( |
| @@ -361,6 +381,85 @@ void WorkerThread::initializeOnWorkerThread(PassOwnPtr<WorkerThreadStartupData> |
| postInitialize(); |
| } |
| +void WorkerThread::terminateInternal(TerminationMode mode) |
| +{ |
| + // 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(); |
| + m_workerGlobalScope->scriptController()->willScheduleExecutionTermination(); |
| + isolate()->TerminateExecution(); |
| + 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::TerminatedBeforeStarting; |
| + return; |
| + } |
| + |
| + // Determine if we should synchronously terminate or schedule to 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 shouldScheduleToTerminateIsolate = !m_readyToShutdown && (workerBackingThread().workerScriptCount() == 1) && !m_runningDebuggerTask; |
| + |
| + if (shouldScheduleToTerminateIsolate) { |
| + if (mode == TerminationMode::Forcible) { |
| + m_workerGlobalScope->scriptController()->willScheduleExecutionTermination(); |
| + isolate()->TerminateExecution(); |
| + DCHECK_EQ(ExitCode::NotTerminated, m_exitCode); |
| + m_exitCode = ExitCode::SyncForciblyTerminated; |
| + } else { |
| + DCHECK_EQ(TerminationMode::Graceful, mode); |
| + 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::prepareForShutdownOnWorkerThread() |
| { |
| DCHECK(isCurrentThread()); |
| @@ -369,6 +468,8 @@ void WorkerThread::prepareForShutdownOnWorkerThread() |
| if (m_readyToShutdown) |
| return; |
| m_readyToShutdown = true; |
| + if (m_exitCode == ExitCode::NotTerminated) |
| + m_exitCode = ExitCode::GracefullyTerminated; |
| } |
| workerReportingProxy().willDestroyWorkerGlobalScope(); |
| @@ -460,4 +561,10 @@ void WorkerThread::runDebuggerTaskDontWaitOnWorkerThread() |
| (*task)(); |
| } |
| +WorkerThread::ExitCode WorkerThread::exitCode() |
| +{ |
| + MutexLocker lock(m_threadStateMutex); |
| + return m_exitCode; |
| +} |
| + |
| } // namespace blink |