#include "core/workers/WorkerThread.h"
-#include "bindings/v8/ScriptSourceCode.h"
+#include "bindings/core/v8/ScriptSourceCode.h"
+#include "core/dom/Microtask.h"
#include "core/inspector/InspectorInstrumentation.h"
-#include "core/platform/ThreadGlobalData.h"
+#include "core/inspector/WorkerInspectorController.h"
#include "core/workers/DedicatedWorkerGlobalScope.h"
#include "core/workers/WorkerClients.h"
+#include "core/workers/WorkerReportingProxy.h"
#include "core/workers/WorkerThreadStartupData.h"
-#include "modules/webdatabase/DatabaseManager.h"
-#include "modules/webdatabase/DatabaseTask.h"
#include "platform/PlatformThreadData.h"
+#include "platform/Task.h"
+#include "platform/ThreadTimers.h"
+#include "platform/heap/ThreadState.h"
+#include "platform/weborigin/KURL.h"
#include "public/platform/Platform.h"
+#include "public/platform/WebThread.h"
+#include "public/platform/WebWaitableEvent.h"
#include "public/platform/WebWorkerRunLoop.h"
-#include "weborigin/KURL.h"
#include "wtf/Noncopyable.h"
#include "wtf/text/WTFString.h"
#include <utility>
-namespace WebCore {
+namespace blink {
+
+namespace {
+const int64 kShortIdleHandlerDelayMs = 1000;
+const int64 kLongIdleHandlerDelayMs = 10*1000;
+
+class MicrotaskRunner : public WebThread::TaskObserver {
+public:
+ virtual void willProcessTask() OVERRIDE { }
+ virtual void didProcessTask() OVERRIDE
+ {
+ Microtask::performCheckpoint();
+ }
+};
+
+} // namespace
static Mutex& threadSetMutex()
{
return workerThreads().size();
}
-WorkerThread::WorkerThread(WorkerLoaderProxy& workerLoaderProxy, WorkerReportingProxy& workerReportingProxy, PassOwnPtr<WorkerThreadStartupData> startupData)
- : m_threadID(0)
+class WorkerSharedTimer : public SharedTimer {
+public:
+ explicit WorkerSharedTimer(WorkerThread* workerThread)
+ : m_workerThread(workerThread)
+ , m_nextFireTime(0.0)
+ , m_running(false)
+ { }
+
+ typedef void (*SharedTimerFunction)();
+ virtual void setFiredFunction(SharedTimerFunction func)
+ {
+ m_sharedTimerFunction = func;
+ if (!m_sharedTimerFunction)
+ m_nextFireTime = 0.0;
+ }
+
+ virtual void setFireInterval(double interval)
+ {
+ ASSERT(m_sharedTimerFunction);
+
+ // See BlinkPlatformImpl::setSharedTimerFireInterval for explanation of
+ // why ceil is used in the interval calculation.
+ int64 delay = static_cast<int64>(ceil(interval * 1000));
+
+ if (delay < 0) {
+ delay = 0;
+ m_nextFireTime = 0.0;
+ }
+
+ m_running = true;
+ m_nextFireTime = currentTime() + interval;
+ m_workerThread->postDelayedTask(createSameThreadTask(&WorkerSharedTimer::OnTimeout, this), delay);
+ }
+
+ virtual void stop()
+ {
+ m_running = false;
+ }
+
+ double nextFireTime() { return m_nextFireTime; }
+
+private:
+ void OnTimeout()
+ {
+ ASSERT(m_workerThread->workerGlobalScope());
+ if (m_sharedTimerFunction && m_running && !m_workerThread->workerGlobalScope()->isClosing())
+ m_sharedTimerFunction();
+ }
+
+ WorkerThread* m_workerThread;
+ SharedTimerFunction m_sharedTimerFunction;
+ double m_nextFireTime;
+ bool m_running;
+};
+
+class WorkerThreadTask : public blink::WebThread::Task {
+ WTF_MAKE_NONCOPYABLE(WorkerThreadTask); WTF_MAKE_FAST_ALLOCATED;
+public:
+ static PassOwnPtr<WorkerThreadTask> create(const WorkerThread& workerThread, PassOwnPtr<ExecutionContextTask> task, bool isInstrumented)
+ {
+ return adoptPtr(new WorkerThreadTask(workerThread, task, isInstrumented));
+ }
+
+ virtual ~WorkerThreadTask() { }
+
+ virtual void run() OVERRIDE
+ {
+ WorkerGlobalScope* workerGlobalScope = m_workerThread.workerGlobalScope();
+ // Tasks could be put on the message loop after the cleanup task,
+ // ensure none of those are ran.
+ if (!workerGlobalScope)
+ return;
+
+ if (m_isInstrumented)
+ InspectorInstrumentation::willPerformExecutionContextTask(workerGlobalScope, m_task.get());
+ if ((!workerGlobalScope->isClosing() && !m_workerThread.terminated()) || m_task->isCleanupTask())
+ m_task->performTask(workerGlobalScope);
+ if (m_isInstrumented)
+ InspectorInstrumentation::didPerformExecutionContextTask(workerGlobalScope);
+ }
+
+private:
+ WorkerThreadTask(const WorkerThread& workerThread, PassOwnPtr<ExecutionContextTask> task, bool isInstrumented)
+ : m_workerThread(workerThread)
+ , m_task(task)
+ , m_isInstrumented(isInstrumented)
+ {
+ if (m_isInstrumented)
+ m_isInstrumented = !m_task->taskNameForInstrumentation().isEmpty();
+ if (m_isInstrumented)
+ InspectorInstrumentation::didPostExecutionContextTask(m_workerThread.workerGlobalScope(), m_task.get());
+ }
+
+ const WorkerThread& m_workerThread;
+ OwnPtr<ExecutionContextTask> m_task;
+ bool m_isInstrumented;
+};
+
+class RunDebuggerQueueTask FINAL : public ExecutionContextTask {
+public:
+ static PassOwnPtr<RunDebuggerQueueTask> create(WorkerThread* thread)
+ {
+ return adoptPtr(new RunDebuggerQueueTask(thread));
+ }
+ virtual void performTask(ExecutionContext* context) OVERRIDE
+ {
+ ASSERT(context->isWorkerGlobalScope());
+ m_thread->runDebuggerTask(WorkerThread::DontWaitForMessage);
+ }
+
+private:
+ explicit RunDebuggerQueueTask(WorkerThread* thread) : m_thread(thread) { }
+
+ WorkerThread* m_thread;
+};
+
+WorkerThread::WorkerThread(WorkerLoaderProxy& workerLoaderProxy, WorkerReportingProxy& workerReportingProxy, PassOwnPtrWillBeRawPtr<WorkerThreadStartupData> startupData)
+ : m_terminated(false)
, m_workerLoaderProxy(workerLoaderProxy)
, m_workerReportingProxy(workerReportingProxy)
, m_startupData(startupData)
- , m_notificationClient(0)
+ , m_shutdownEvent(adoptPtr(blink::Platform::current()->createWaitableEvent()))
+ , m_terminationEvent(adoptPtr(blink::Platform::current()->createWaitableEvent()))
{
MutexLocker lock(threadSetMutex());
workerThreads().add(this);
workerThreads().remove(this);
}
-bool WorkerThread::start()
+void WorkerThread::start()
{
- // Mutex protection is necessary to ensure that m_threadID is initialized when the thread starts.
- MutexLocker lock(m_threadCreationMutex);
-
- if (m_threadID)
- return true;
-
- m_threadID = createThread(WorkerThread::workerThreadStart, this, "WebCore: Worker");
+ if (m_thread)
+ return;
- return m_threadID;
+ m_thread = adoptPtr(blink::Platform::current()->createThread("WebCore: Worker"));
+ m_thread->postTask(new Task(WTF::bind(&WorkerThread::initialize, this)));
}
-void WorkerThread::workerThreadStart(void* thread)
+void WorkerThread::interruptAndDispatchInspectorCommands()
{
- static_cast<WorkerThread*>(thread)->workerThread();
+ MutexLocker locker(m_workerInspectorControllerMutex);
+ if (m_workerInspectorController)
+ m_workerInspectorController->interruptAndDispatchInspectorCommands();
}
-void WorkerThread::workerThread()
+void WorkerThread::initialize()
{
KURL scriptURL = m_startupData->m_scriptURL;
String sourceCode = m_startupData->m_sourceCode;
{
MutexLocker lock(m_threadCreationMutex);
+ // The worker was terminated before the thread had a chance to run.
+ if (m_terminated) {
+ // Notify the proxy that the WorkerGlobalScope has been disposed of.
+ // This can free this thread object, hence it must not be touched afterwards.
+ m_workerReportingProxy.workerThreadTerminated();
+ return;
+ }
+
+ m_microtaskRunner = adoptPtr(new MicrotaskRunner);
+ m_thread->addTaskObserver(m_microtaskRunner.get());
+ m_pendingGCRunner = adoptPtr(new PendingGCRunner);
+ m_messageLoopInterruptor = adoptPtr(new MessageLoopInterruptor(m_thread.get()));
+ m_thread->addTaskObserver(m_pendingGCRunner.get());
+ ThreadState::attach();
+ ThreadState::current()->addInterruptor(m_messageLoopInterruptor.get());
m_workerGlobalScope = createWorkerGlobalScope(m_startupData.release());
- 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().
- m_workerGlobalScope->script()->forbidExecution();
- }
+ m_sharedTimer = adoptPtr(new WorkerSharedTimer(this));
+ PlatformThreadData::current().threadTimers().setSharedTimer(m_sharedTimer.get());
}
+
// The corresponding call to didStopWorkerRunLoop is in
// ~WorkerScriptController.
- WebKit::Platform::current()->didStartWorkerRunLoop(WebKit::WebWorkerRunLoop(&m_runLoop));
+ blink::Platform::current()->didStartWorkerRunLoop(blink::WebWorkerRunLoop(this));
+
+ // Notify proxy that a new WorkerGlobalScope has been created and started.
+ m_workerReportingProxy.workerGlobalScopeStarted(m_workerGlobalScope.get());
WorkerScriptController* script = m_workerGlobalScope->script();
+ if (!script->isExecutionForbidden())
+ script->initializeContextIfNeeded();
InspectorInstrumentation::willEvaluateWorkerScript(workerGlobalScope(), startMode);
script->evaluate(ScriptSourceCode(sourceCode, scriptURL));
- runEventLoop();
+ postInitialize();
- ThreadIdentifier threadID = m_threadID;
+ postDelayedTask(createSameThreadTask(&WorkerThread::idleHandler, this), kShortIdleHandlerDelayMs);
+}
- ASSERT(m_workerGlobalScope->hasOneRef());
+void WorkerThread::cleanup()
+{
+
+ // This should be called before we start the shutdown procedure.
+ workerReportingProxy().willDestroyWorkerGlobalScope();
// 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.
- m_workerGlobalScope = 0;
+ // If Oilpan is enabled, we detach of the context/global scope, with the final heap cleanup below sweeping it out.
+#if !ENABLE(OILPAN)
+ ASSERT(m_workerGlobalScope->hasOneRef());
+#endif
+ m_workerGlobalScope->dispose();
+ m_workerGlobalScope = nullptr;
+
+ ThreadState::current()->removeInterruptor(m_messageLoopInterruptor.get());
+
+ // Detach the ThreadState, cleaning out the thread's heap by
+ // performing a final GC. The cleanup operation will at the end
+ // assert that the heap is empty. If the heap does not become
+ // empty, there are still pointers into the heap and those
+ // pointers will be dangling after thread termination because we
+ // are destroying the heap. It is important to detach while the
+ // thread is still valid. In particular, finalizers for objects in
+ // the heap for this thread will need to access thread local data.
+ ThreadState::detach();
+
+ m_thread->removeTaskObserver(m_microtaskRunner.get());
+ m_microtaskRunner = nullptr;
+ m_thread->removeTaskObserver(m_pendingGCRunner.get());
+ m_pendingGCRunner = nullptr;
+ m_messageLoopInterruptor = nullptr;
+
+ // Notify the proxy that the WorkerGlobalScope has been disposed of.
+ // This can free this thread object, hence it must not be touched afterwards.
+ workerReportingProxy().workerThreadTerminated();
+
+ m_terminationEvent->signal();
// Clean up PlatformThreadData before WTF::WTFThreadData goes away!
PlatformThreadData::current().destroy();
-
- // The thread object may be already destroyed from notification now, don't try to access "this".
- detachThread(threadID);
-}
-
-void WorkerThread::runEventLoop()
-{
- // Does not return until terminated.
- m_runLoop.run(m_workerGlobalScope.get());
}
class WorkerThreadShutdownFinishTask : public ExecutionContextTask {
workerGlobalScope->clearInspector();
// It's not safe to call clearScript until all the cleanup tasks posted by functions initiated by WorkerThreadShutdownStartTask have completed.
workerGlobalScope->clearScript();
+ workerGlobalScope->thread()->m_thread->postTask(new Task(WTF::bind(&WorkerThread::cleanup, workerGlobalScope->thread())));
}
virtual bool isCleanupTask() const { return true; }
virtual void performTask(ExecutionContext *context)
{
WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
-
- // FIXME: Should we stop the databases as part of stopActiveDOMObjects() below?
- DatabaseTaskSynchronizer cleanupSync;
- DatabaseManager::manager().stopDatabases(workerGlobalScope, &cleanupSync);
-
+ workerGlobalScope->stopFetch();
workerGlobalScope->stopActiveDOMObjects();
-
- workerGlobalScope->notifyObserversOfStop();
+ PlatformThreadData::current().threadTimers().setSharedTimer(nullptr);
// Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects,
// which become dangling once Heap is destroyed.
workerGlobalScope->removeAllEventListeners();
- // We wait for the database thread to clean up all its stuff so that we
- // can do more stringent leak checks as we exit.
- cleanupSync.waitForTaskCompletion();
-
// Stick a shutdown command at the end of the queue, so that we deal
// with all the cleanup tasks the databases post first.
workerGlobalScope->postTask(WorkerThreadShutdownFinishTask::create());
void WorkerThread::stop()
{
- // Mutex protection is necessary because stop() can be called before the context is fully created.
+ // Prevent the deadlock between GC and an attempt to stop a thread.
+ ThreadState::SafePointScope safePointScope(ThreadState::HeapPointersOnStack);
+ stopInternal();
+}
+
+void WorkerThread::stopInShutdownSequence()
+{
+ stopInternal();
+}
+
+void WorkerThread::stopInternal()
+{
+ // Protect against this method and initialize() racing each other.
MutexLocker lock(m_threadCreationMutex);
- // 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 (m_workerGlobalScope) {
- m_workerGlobalScope->script()->scheduleExecutionTermination();
+ // If stop 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_shutdownEvent)
+ m_shutdownEvent->signal();
- DatabaseManager::manager().interruptAllDatabasesForContext(m_workerGlobalScope.get());
- m_runLoop.postTaskAndTerminate(WorkerThreadShutdownStartTask::create());
+ if (!m_workerGlobalScope)
return;
- }
- m_runLoop.terminate();
+
+ // 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.
+ m_workerGlobalScope->script()->scheduleExecutionTermination();
+ m_workerGlobalScope->wasRequestedToTerminate();
+ InspectorInstrumentation::didKillAllExecutionContextTasks(m_workerGlobalScope.get());
+ m_debuggerMessageQueue.kill();
+ postTask(WorkerThreadShutdownStartTask::create());
+}
+
+void WorkerThread::terminateAndWaitForAllWorkers()
+{
+ // Keep this lock to prevent WorkerThread instances from being destroyed.
+ MutexLocker lock(threadSetMutex());
+ HashSet<WorkerThread*> threads = workerThreads();
+ for (HashSet<WorkerThread*>::iterator itr = threads.begin(); itr != threads.end(); ++itr)
+ (*itr)->stopInShutdownSequence();
+
+ for (HashSet<WorkerThread*>::iterator itr = threads.begin(); itr != threads.end(); ++itr)
+ (*itr)->terminationEvent()->wait();
}
bool WorkerThread::isCurrentThread() const
{
- return m_threadID == currentThread();
+ return m_thread && m_thread->isCurrentThread();
}
-class ReleaseFastMallocFreeMemoryTask : public ExecutionContextTask {
- virtual void performTask(ExecutionContext*) OVERRIDE { WTF::releaseFastMallocFreeMemory(); }
-};
+void WorkerThread::idleHandler()
+{
+ ASSERT(m_workerGlobalScope.get());
+ int64 delay = kLongIdleHandlerDelayMs;
+
+ // Do a script engine idle notification if the next event is distant enough.
+ const double kMinIdleTimespan = 0.3;
+ if (m_sharedTimer->nextFireTime() == 0.0 || m_sharedTimer->nextFireTime() > currentTime() + kMinIdleTimespan) {
+ bool hasMoreWork = !m_workerGlobalScope->idleNotification();
+ if (hasMoreWork)
+ delay = kShortIdleHandlerDelayMs;
+ }
+
+ postDelayedTask(createSameThreadTask(&WorkerThread::idleHandler, this), delay);
+}
-void WorkerThread::releaseFastMallocFreeMemoryInAllThreads()
+void WorkerThread::postTask(PassOwnPtr<ExecutionContextTask> task)
{
- MutexLocker lock(threadSetMutex());
- HashSet<WorkerThread*>& threads = workerThreads();
- HashSet<WorkerThread*>::iterator end = threads.end();
- for (HashSet<WorkerThread*>::iterator it = threads.begin(); it != end; ++it)
- (*it)->runLoop().postTask(adoptPtr(new ReleaseFastMallocFreeMemoryTask));
+ m_thread->postTask(WorkerThreadTask::create(*this, task, true).leakPtr());
+}
+
+void WorkerThread::postDelayedTask(PassOwnPtr<ExecutionContextTask> task, long long delayMs)
+{
+ m_thread->postDelayedTask(WorkerThreadTask::create(*this, task, true).leakPtr(), delayMs);
+}
+
+void WorkerThread::postDebuggerTask(PassOwnPtr<ExecutionContextTask> task)
+{
+ m_debuggerMessageQueue.append(WorkerThreadTask::create(*this, task, false));
+ postTask(RunDebuggerQueueTask::create(this));
+}
+
+MessageQueueWaitResult WorkerThread::runDebuggerTask(WaitMode waitMode)
+{
+ ASSERT(isCurrentThread());
+ MessageQueueWaitResult result;
+ double absoluteTime = MessageQueue<blink::WebThread::Task>::infiniteTime();
+ OwnPtr<blink::WebThread::Task> task;
+ {
+ if (waitMode == DontWaitForMessage)
+ absoluteTime = 0.0;
+ ThreadState::SafePointScope safePointScope(ThreadState::NoHeapPointersOnStack);
+ task = m_debuggerMessageQueue.waitForMessageWithTimeout(result, absoluteTime);
+ }
+
+ if (result == MessageQueueMessageReceived) {
+ InspectorInstrumentation::willProcessTask(workerGlobalScope());
+ task->run();
+ InspectorInstrumentation::didProcessTask(workerGlobalScope());
+ }
+
+ return result;
+}
+
+void WorkerThread::willEnterNestedLoop()
+{
+ InspectorInstrumentation::willEnterNestedRunLoop(m_workerGlobalScope.get());
+}
+
+void WorkerThread::didLeaveNestedLoop()
+{
+ InspectorInstrumentation::didLeaveNestedRunLoop(m_workerGlobalScope.get());
+}
+
+void WorkerThread::setWorkerInspectorController(WorkerInspectorController* workerInspectorController)
+{
+ MutexLocker locker(m_workerInspectorControllerMutex);
+ m_workerInspectorController = workerInspectorController;
}
-} // namespace WebCore
+} // namespace blink