Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: third_party/WebKit/Source/core/workers/WorkerThread.cpp

Issue 2011763002: Worker: Attempt to gracefully terminate WorkerThread as much as possible (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@reorder_functions
Patch Set: address review comments Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions 5 * modification, are permitted provided that the following conditions
6 * are met: 6 * are met:
7 * 1. Redistributions of source code must retain the above copyright 7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer. 8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright 9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the 10 * notice, this list of conditions and the following disclaimer in the
(...skipping 24 matching lines...) Expand all
35 #include "core/inspector/WorkerThreadDebugger.h" 35 #include "core/inspector/WorkerThreadDebugger.h"
36 #include "core/workers/WorkerBackingThread.h" 36 #include "core/workers/WorkerBackingThread.h"
37 #include "core/workers/WorkerClients.h" 37 #include "core/workers/WorkerClients.h"
38 #include "core/workers/WorkerReportingProxy.h" 38 #include "core/workers/WorkerReportingProxy.h"
39 #include "core/workers/WorkerThreadStartupData.h" 39 #include "core/workers/WorkerThreadStartupData.h"
40 #include "platform/ThreadSafeFunctional.h" 40 #include "platform/ThreadSafeFunctional.h"
41 #include "platform/WaitableEvent.h" 41 #include "platform/WaitableEvent.h"
42 #include "platform/WebThreadSupportingGC.h" 42 #include "platform/WebThreadSupportingGC.h"
43 #include "platform/heap/SafePoint.h" 43 #include "platform/heap/SafePoint.h"
44 #include "platform/heap/ThreadState.h" 44 #include "platform/heap/ThreadState.h"
45 #include "platform/scheduler/CancellableTaskFactory.h"
45 #include "platform/weborigin/KURL.h" 46 #include "platform/weborigin/KURL.h"
46 #include "public/platform/WebThread.h" 47 #include "public/platform/WebThread.h"
47 #include "wtf/Functional.h" 48 #include "wtf/Functional.h"
48 #include "wtf/Noncopyable.h" 49 #include "wtf/Noncopyable.h"
49 #include "wtf/text/WTFString.h" 50 #include "wtf/text/WTFString.h"
50 #include <limits.h> 51 #include <limits.h>
51 52
52 namespace blink { 53 namespace blink {
53 54
54 class WorkerThread::WorkerMicrotaskRunner : public WebThread::TaskObserver { 55 // TODO(nhiroki): Adjust the delay based on UMA.
56 const long long kForceTerminationDelayInMs = 3000; // 3 secs
kinuko 2016/05/31 10:13:26 nit: we used to have 2 secs timer to forcibly kill
nhiroki 2016/06/01 04:16:36 Done.
57
58 // ForceTerminationTask is used for posting a delayed task to terminate an
59 // isolate from the main thread. This task is expected to run when the shutdown
60 // sequence does not start in a certain time period because of an inifite loop
61 // in the JS execution context etc. When the shutdown sequence is started before
62 // this task runs, the task is simply cancelled.
63 class WorkerThread::ForceTerminationTask final {
64 public:
65 static PassOwnPtr<ForceTerminationTask> create(WorkerThread* workerThread)
66 {
67 return adoptPtr(new ForceTerminationTask(workerThread));
68 }
69
70 void schedule()
71 {
72 DCHECK(isMainThread());
73 Platform::current()->mainThread()->getWebTaskRunner()->postDelayedTask(B LINK_FROM_HERE, m_cancellableTaskFactory->cancelAndCreate(), m_workerThread->m_f orceTerminationDelayInMs);
74 }
75
76 private:
77 explicit ForceTerminationTask(WorkerThread* workerThread)
78 : m_workerThread(workerThread)
79 {
80 DCHECK(isMainThread());
81 m_cancellableTaskFactory = CancellableTaskFactory::create(this, &ForceTe rminationTask::run);
82 }
83
84 void run()
85 {
86 DCHECK(isMainThread());
87 MutexLocker lock(m_workerThread->m_threadStateMutex);
88 if (m_workerThread->m_readyToShutdown) {
89 // Shutdown sequence is now running. Just return.
90 return;
91 }
92
93 m_workerThread->forciblyTerminateIsolate();
94 DCHECK_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->m_exitC ode);
95 m_workerThread->m_exitCode = WorkerThread::ExitCode::AsyncForciblyTermin ated;
96 }
97
98 WorkerThread* m_workerThread;
99 OwnPtr<CancellableTaskFactory> m_cancellableTaskFactory;
100 };
101
102 class WorkerThread::WorkerMicrotaskRunner final : public WebThread::TaskObserver {
55 public: 103 public:
56 explicit WorkerMicrotaskRunner(WorkerThread* workerThread) 104 explicit WorkerMicrotaskRunner(WorkerThread* workerThread)
57 : m_workerThread(workerThread) 105 : m_workerThread(workerThread)
58 { 106 {
59 } 107 }
60 108
61 void willProcessTask() override 109 void willProcessTask() override
62 { 110 {
63 // No tasks should get executed after we have closed. 111 // No tasks should get executed after we have closed.
64 DCHECK(!m_workerThread->workerGlobalScope() || !m_workerThread->workerGl obalScope()->isClosing()); 112 DCHECK(!m_workerThread->workerGlobalScope() || !m_workerThread->workerGl obalScope()->isClosing());
(...skipping 28 matching lines...) Expand all
93 } 141 }
94 142
95 static HashSet<WorkerThread*>& workerThreads() 143 static HashSet<WorkerThread*>& workerThreads()
96 { 144 {
97 DEFINE_STATIC_LOCAL(HashSet<WorkerThread*>, threads, ()); 145 DEFINE_STATIC_LOCAL(HashSet<WorkerThread*>, threads, ());
98 return threads; 146 return threads;
99 } 147 }
100 148
101 WorkerThread::~WorkerThread() 149 WorkerThread::~WorkerThread()
102 { 150 {
151 DCHECK(isMainThread());
103 MutexLocker lock(threadSetMutex()); 152 MutexLocker lock(threadSetMutex());
104 DCHECK(workerThreads().contains(this)); 153 DCHECK(workerThreads().contains(this));
105 workerThreads().remove(this); 154 workerThreads().remove(this);
155
156 // TODO(nhiroki): Record how this thread is terminated (i.e. m_exitCode)
157 // in UMA.
158 DCHECK_NE(ExitCode::NotTerminated, m_exitCode);
106 } 159 }
107 160
108 void WorkerThread::start(PassOwnPtr<WorkerThreadStartupData> startupData) 161 void WorkerThread::start(PassOwnPtr<WorkerThreadStartupData> startupData)
109 { 162 {
110 DCHECK(isMainThread()); 163 DCHECK(isMainThread());
111 164
112 if (m_started) 165 if (m_started)
113 return; 166 return;
114 167
115 m_started = true; 168 m_started = true;
116 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::initializeOnWorkerThread, AllowCrossThreadAccess(this), passed (std::move(startupData)))); 169 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::initializeOnWorkerThread, AllowCrossThreadAccess(this), passed (std::move(startupData))));
117 } 170 }
118 171
119 void WorkerThread::terminate() 172 void WorkerThread::terminate()
120 { 173 {
121 DCHECK(isMainThread()); 174 DCHECK(isMainThread());
122 175 terminateInternal(TerminationMode::Graceful);
123 // Prevent the deadlock between GC and an attempt to terminate a thread.
124 SafePointScope safePointScope(BlinkGC::HeapPointersOnStack);
125
126 // Protect against this method, initializeOnWorkerThread() or termination
127 // via the global scope racing each other.
128 MutexLocker lock(m_threadStateMutex);
129
130 // If terminate has already been called, just return.
131 if (m_terminated)
132 return;
133 m_terminated = true;
134
135 // Signal the thread to notify that the thread's stopping.
136 if (m_terminationEvent)
137 m_terminationEvent->signal();
138
139 // If the worker thread was never initialized, don't start another
140 // shutdown, but still wait for the thread to signal when shutdown has
141 // completed on initializeOnWorkerThread().
142 if (!m_workerGlobalScope)
143 return;
144
145 // Determine if we should terminate the isolate so that the task can
146 // be handled by thread event loop. If script execution weren't forbidden,
147 // a while(1) loop in JS could keep the thread alive forever.
148 //
149 // (1) |m_readyToShutdown|: It this is set, the worker thread has already
150 // noticed that the thread is about to be terminated and the worker global
151 // scope is already disposed, so we don't have to explicitly terminate the
152 // isolate.
153 //
154 // (2) |workerScriptCount() == 1|: If other WorkerGlobalScopes are running
155 // on the worker thread, we should not terminate the isolate. This condition
156 // is not entirely correct because other scripts can be being initialized or
157 // terminated simuletaneously. Though this function itself is protected by a
158 // mutex, it is possible that |workerScriptCount()| here is not consistent
159 // with that in |initialize| and |shutdown|.
160 //
161 // (3) |m_runningDebuggerTask|: Terminating during debugger task may lead to
162 // crash due to heavy use of v8 api in debugger. Any debugger task is
163 // guaranteed to finish, so we can wait for the completion.
164 bool shouldTerminateIsolate = !m_readyToShutdown && (workerBackingThread().w orkerScriptCount() == 1) && !m_runningDebuggerTask;
165
166 if (shouldTerminateIsolate) {
167 // TODO(yhirano): TerminateExecution should be called more
168 // carefully (https://crbug.com/413518).
169 m_workerGlobalScope->scriptController()->willScheduleExecutionTerminatio n();
170 isolate()->TerminateExecution();
171 }
172
173 m_inspectorTaskRunner->kill();
174 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::prepareForShutdownOnWorkerThread, AllowCrossThreadAccess(this) ));
175 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::performShutdownOnWorkerThread, AllowCrossThreadAccess(this)));
176 } 176 }
177 177
178 void WorkerThread::terminateAndWait() 178 void WorkerThread::terminateAndWait()
179 { 179 {
180 DCHECK(isMainThread()); 180 DCHECK(isMainThread());
181 terminate(); 181
182 // The main thread will be blocked, so asynchronous graceful shutdown does
183 // not work.
184 terminateInternal(TerminationMode::Forcible);
185
186 // Terminating the worker thread that hasn't started yet does not run the
187 // shutdown sequence on the worker thread, so should not wait for the
188 // shutdown event.
189 if (m_exitCode == ExitCode::TerminatedBeforeStarting)
190 return;
182 m_shutdownEvent->wait(); 191 m_shutdownEvent->wait();
183 } 192 }
184 193
185 void WorkerThread::terminateAndWaitForAllWorkers() 194 void WorkerThread::terminateAndWaitForAllWorkers()
186 { 195 {
187 DCHECK(isMainThread()); 196 DCHECK(isMainThread());
188 197
189 // Keep this lock to prevent WorkerThread instances from being destroyed. 198 // Keep this lock to prevent WorkerThread instances from being destroyed.
190 MutexLocker lock(threadSetMutex()); 199 MutexLocker lock(threadSetMutex());
191 HashSet<WorkerThread*> threads = workerThreads(); 200 HashSet<WorkerThread*> threads = workerThreads();
201
202 // The main thread will be blocked, so asynchronous graceful shutdown does
203 // not work.
192 for (WorkerThread* thread : threads) 204 for (WorkerThread* thread : threads)
193 thread->terminate(); 205 thread->terminateInternal(TerminationMode::Forcible);
194 206
195 for (WorkerThread* thread : threads) 207 for (WorkerThread* thread : threads) {
208 // Terminating the worker thread that hasn't started yet does not run
209 // the shutdown sequence on the worker thread, so should not wait for
210 // the shutdown event.
211 if (thread->m_exitCode == ExitCode::TerminatedBeforeStarting)
212 continue;
196 thread->m_shutdownEvent->wait(); 213 thread->m_shutdownEvent->wait();
214 }
197 } 215 }
198 216
199 v8::Isolate* WorkerThread::isolate() 217 v8::Isolate* WorkerThread::isolate()
200 { 218 {
201 return workerBackingThread().isolate(); 219 return workerBackingThread().isolate();
202 } 220 }
203 221
204 bool WorkerThread::isCurrentThread() 222 bool WorkerThread::isCurrentThread()
205 { 223 {
206 return m_started && workerBackingThread().backingThread().isCurrentThread(); 224 return m_started && workerBackingThread().backingThread().isCurrentThread();
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
268 } 286 }
269 287
270 PlatformThreadId WorkerThread::platformThreadId() 288 PlatformThreadId WorkerThread::platformThreadId()
271 { 289 {
272 if (!m_started) 290 if (!m_started)
273 return 0; 291 return 0;
274 return workerBackingThread().backingThread().platformThread().threadId(); 292 return workerBackingThread().backingThread().platformThread().threadId();
275 } 293 }
276 294
277 WorkerThread::WorkerThread(PassRefPtr<WorkerLoaderProxy> workerLoaderProxy, Work erReportingProxy& workerReportingProxy) 295 WorkerThread::WorkerThread(PassRefPtr<WorkerLoaderProxy> workerLoaderProxy, Work erReportingProxy& workerReportingProxy)
278 : m_inspectorTaskRunner(adoptPtr(new InspectorTaskRunner())) 296 : m_forceTerminationDelayInMs(kForceTerminationDelayInMs)
297 , m_inspectorTaskRunner(adoptPtr(new InspectorTaskRunner()))
279 , m_workerLoaderProxy(workerLoaderProxy) 298 , m_workerLoaderProxy(workerLoaderProxy)
280 , m_workerReportingProxy(workerReportingProxy) 299 , m_workerReportingProxy(workerReportingProxy)
281 , m_terminationEvent(adoptPtr(new WaitableEvent( 300 , m_terminationEvent(adoptPtr(new WaitableEvent(
282 WaitableEvent::ResetPolicy::Manual, 301 WaitableEvent::ResetPolicy::Manual,
283 WaitableEvent::InitialState::NonSignaled))) 302 WaitableEvent::InitialState::NonSignaled)))
284 , m_shutdownEvent(adoptPtr(new WaitableEvent( 303 , m_shutdownEvent(adoptPtr(new WaitableEvent(
285 WaitableEvent::ResetPolicy::Manual, 304 WaitableEvent::ResetPolicy::Manual,
286 WaitableEvent::InitialState::NonSignaled))) 305 WaitableEvent::InitialState::NonSignaled)))
287 { 306 {
288 MutexLocker lock(threadSetMutex()); 307 MutexLocker lock(threadSetMutex());
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
354 } 373 }
355 374
356 CachedMetadataHandler* handler = workerGlobalScope()->createWorkerScriptCach edMetadataHandler(scriptURL, cachedMetaData.get()); 375 CachedMetadataHandler* handler = workerGlobalScope()->createWorkerScriptCach edMetadataHandler(scriptURL, cachedMetaData.get());
357 bool success = m_workerGlobalScope->scriptController()->evaluate(ScriptSourc eCode(sourceCode, scriptURL), nullptr, handler, v8CacheOptions); 376 bool success = m_workerGlobalScope->scriptController()->evaluate(ScriptSourc eCode(sourceCode, scriptURL), nullptr, handler, v8CacheOptions);
358 m_workerGlobalScope->didEvaluateWorkerScript(); 377 m_workerGlobalScope->didEvaluateWorkerScript();
359 m_workerReportingProxy.didEvaluateWorkerScript(success); 378 m_workerReportingProxy.didEvaluateWorkerScript(success);
360 379
361 postInitialize(); 380 postInitialize();
362 } 381 }
363 382
383 void WorkerThread::terminateInternal(TerminationMode mode)
384 {
385 // Prevent the deadlock between GC and an attempt to terminate a thread.
386 SafePointScope safePointScope(BlinkGC::HeapPointersOnStack);
387
388 // Protect against this method, initializeOnWorkerThread() or termination
389 // via the global scope racing each other.
390 MutexLocker lock(m_threadStateMutex);
391
392 // If terminate has already been called.
393 if (m_terminated) {
394 // The synchronous forcible termination request should overtake the
395 // scheduled termination task because the request will block the main
396 // thread and the scheduled termination task never runs.
397 if (mode == TerminationMode::Forcible && m_exitCode == ExitCode::NotTerm inated) {
398 DCHECK(m_scheduledForceTerminationTask);
399 m_scheduledForceTerminationTask.reset();
400 forciblyTerminateIsolate();
401 DCHECK_EQ(ExitCode::NotTerminated, m_exitCode);
402 m_exitCode = ExitCode::SyncForciblyTerminated;
403 }
404 return;
405 }
406 m_terminated = true;
407
408 // Signal the thread to notify that the thread's stopping.
409 if (m_terminationEvent)
410 m_terminationEvent->signal();
411
412 // If the worker thread was never initialized, don't start another
413 // shutdown, but still wait for the thread to signal when shutdown has
414 // completed on initializeOnWorkerThread().
kinuko 2016/05/31 10:13:26 Hmm, if we're not calling start() initializeOnWork
kinuko 2016/05/31 10:13:26 The comment looks stale now? (We're no longer wait
nhiroki 2016/06/01 04:16:36 Good point. I'm now thinking that the main thread
415 if (!m_workerGlobalScope) {
416 DCHECK_EQ(ExitCode::NotTerminated, m_exitCode);
417 m_exitCode = ExitCode::TerminatedBeforeStarting;
418 return;
419 }
420
421 // Determine if we should synchronously terminate or schedule to terminate
422 // the isolate so that the task can be handled by thread event loop. If
423 // script execution weren't forbidden, a while(1) loop in JS could keep
424 // the thread alive forever.
425 //
426 // (1) |m_readyToShutdown|: It this is set, the worker thread has already
427 // noticed that the thread is about to be terminated and the worker global
428 // scope is already disposed, so we don't have to explicitly terminate the
429 // isolate.
430 //
431 // (2) |workerScriptCount() == 1|: If other WorkerGlobalScopes are running
432 // on the worker thread, we should not terminate the isolate. This condition
433 // is not entirely correct because other scripts can be being initialized or
434 // terminated simuletaneously. Though this function itself is protected by a
435 // mutex, it is possible that |workerScriptCount()| here is not consistent
436 // with that in |initialize| and |shutdown|.
437 //
438 // (3) |m_runningDebuggerTask|: Terminating during debugger task may lead to
439 // crash due to heavy use of v8 api in debugger. Any debugger task is
440 // guaranteed to finish, so we can wait for the completion.
441 bool shouldScheduleToTerminateIsolate = !m_readyToShutdown && (workerBacking Thread().workerScriptCount() == 1) && !m_runningDebuggerTask;
442
443 if (shouldScheduleToTerminateIsolate) {
444 if (mode == TerminationMode::Forcible) {
445 forciblyTerminateIsolate();
446 DCHECK_EQ(ExitCode::NotTerminated, m_exitCode);
447 m_exitCode = ExitCode::SyncForciblyTerminated;
448 } else {
449 DCHECK_EQ(TerminationMode::Graceful, mode);
kinuko 2016/05/31 10:13:26 nit: DCHECK(!m_scheduledForceTreminationTask) ?
nhiroki 2016/06/01 04:16:36 Done.
450 m_scheduledForceTerminationTask = ForceTerminationTask::create(this) ;
451 m_scheduledForceTerminationTask->schedule();
452 }
453 }
454
455 m_inspectorTaskRunner->kill();
456 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::prepareForShutdownOnWorkerThread, AllowCrossThreadAccess(this) ));
457 workerBackingThread().backingThread().postTask(BLINK_FROM_HERE, threadSafeBi nd(&WorkerThread::performShutdownOnWorkerThread, AllowCrossThreadAccess(this)));
458 }
459
460 void WorkerThread::forciblyTerminateIsolate()
461 {
462 DCHECK(m_workerGlobalScope);
463 m_workerGlobalScope->scriptController()->willScheduleExecutionTermination();
464 isolate()->TerminateExecution();
465 }
466
364 void WorkerThread::prepareForShutdownOnWorkerThread() 467 void WorkerThread::prepareForShutdownOnWorkerThread()
365 { 468 {
366 DCHECK(isCurrentThread()); 469 DCHECK(isCurrentThread());
367 { 470 {
368 MutexLocker lock(m_threadStateMutex); 471 MutexLocker lock(m_threadStateMutex);
369 if (m_readyToShutdown) 472 if (m_readyToShutdown)
370 return; 473 return;
371 m_readyToShutdown = true; 474 m_readyToShutdown = true;
475 if (m_exitCode == ExitCode::NotTerminated)
476 m_exitCode = ExitCode::GracefullyTerminated;
372 } 477 }
373 478
374 workerReportingProxy().willDestroyWorkerGlobalScope(); 479 workerReportingProxy().willDestroyWorkerGlobalScope();
375 InspectorInstrumentation::allAsyncTasksCanceled(workerGlobalScope()); 480 InspectorInstrumentation::allAsyncTasksCanceled(workerGlobalScope());
376 workerGlobalScope()->dispose(); 481 workerGlobalScope()->dispose();
377 workerBackingThread().backingThread().removeTaskObserver(m_microtaskRunner.g et()); 482 workerBackingThread().backingThread().removeTaskObserver(m_microtaskRunner.g et());
378 } 483 }
379 484
380 void WorkerThread::performShutdownOnWorkerThread() 485 void WorkerThread::performShutdownOnWorkerThread()
381 { 486 {
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
453 } 558 }
454 559
455 void WorkerThread::runDebuggerTaskDontWaitOnWorkerThread() 560 void WorkerThread::runDebuggerTaskDontWaitOnWorkerThread()
456 { 561 {
457 DCHECK(isCurrentThread()); 562 DCHECK(isCurrentThread());
458 std::unique_ptr<CrossThreadClosure> task = m_inspectorTaskRunner->takeNextTa sk(InspectorTaskRunner::DontWaitForTask); 563 std::unique_ptr<CrossThreadClosure> task = m_inspectorTaskRunner->takeNextTa sk(InspectorTaskRunner::DontWaitForTask);
459 if (task) 564 if (task)
460 (*task)(); 565 (*task)();
461 } 566 }
462 567
568 WorkerThread::ExitCode WorkerThread::exitCode()
569 {
570 MutexLocker lock(m_threadStateMutex);
571 return m_exitCode;
572 }
573
463 } // namespace blink 574 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698