#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/inspector/WorkerInspectorController.h"
#include "core/workers/DedicatedWorkerGlobalScope.h"
#include "core/workers/WorkerClients.h"
#include "core/workers/WorkerReportingProxy.h"
#include "core/workers/WorkerThreadStartupData.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 "wtf/Noncopyable.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();
}
+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_threadID(0)
+ : m_terminated(false)
, m_workerLoaderProxy(workerLoaderProxy)
, m_workerReportingProxy(workerReportingProxy)
, m_startupData(startupData)
- , m_notificationClient(0)
, m_shutdownEvent(adoptPtr(blink::Platform::current()->createWaitableEvent()))
{
MutexLocker lock(threadSetMutex());
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());
- m_runLoop.setWorkerGlobalScope(workerGlobalScope());
- 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.
- blink::Platform::current()->didStartWorkerRunLoop(blink::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());
InspectorInstrumentation::willEvaluateWorkerScript(workerGlobalScope(), startMode);
script->evaluate(ScriptSourceCode(sourceCode, scriptURL));
- runEventLoop();
+ postInitialize();
+
+ postDelayedTask(createSameThreadTask(&WorkerThread::idleHandler, this), kShortIdleHandlerDelayMs);
+}
+
+void WorkerThread::cleanup()
+{
// This should be called before we start the shutdown procedure.
workerReportingProxy().willDestroyWorkerGlobalScope();
- ThreadIdentifier threadID = m_threadID;
-
// 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_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
// 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().workerGlobalScopeDestroyed();
+ workerReportingProxy().workerThreadTerminated();
// 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();
}
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; }
WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
workerGlobalScope->stopFetch();
workerGlobalScope->stopActiveDOMObjects();
+ 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.
// Prevent the deadlock between GC and an attempt to stop a thread.
ThreadState::SafePointScope safePointScope(ThreadState::HeapPointersOnStack);
- // Mutex protection is necessary because stop() can be called before the context is fully created.
+ // Protect against this method and initialize() racing each other.
MutexLocker lock(m_threadCreationMutex);
+ // 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();
- // 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();
- m_workerGlobalScope->wasRequestedToTerminate();
- 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());
}
bool WorkerThread::isCurrentThread() const
{
- return m_threadID == currentThread();
+ return m_thread && m_thread->isCurrentThread();
+}
+
+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::postTask(PassOwnPtr<ExecutionContextTask> task)
+{
+ 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