michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MediaTaskQueue.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "SharedThreadPool.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: MediaTaskQueue::MediaTaskQueue(TemporaryRef aPool) michael@0: : mPool(aPool) michael@0: , mQueueMonitor("MediaTaskQueue::Queue") michael@0: , mIsRunning(false) michael@0: , mIsShutdown(false) michael@0: { michael@0: MOZ_COUNT_CTOR(MediaTaskQueue); michael@0: } michael@0: michael@0: MediaTaskQueue::~MediaTaskQueue() michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: MOZ_ASSERT(mIsShutdown); michael@0: MOZ_COUNT_DTOR(MediaTaskQueue); michael@0: } michael@0: michael@0: nsresult michael@0: MediaTaskQueue::Dispatch(TemporaryRef aRunnable) michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: if (mIsShutdown) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mTasks.push(aRunnable); michael@0: if (mIsRunning) { michael@0: return NS_OK; michael@0: } michael@0: RefPtr runner(new Runner(this)); michael@0: nsresult rv = mPool->Dispatch(runner, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch runnable to run MediaTaskQueue"); michael@0: return rv; michael@0: } michael@0: mIsRunning = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: MediaTaskQueue::AwaitIdle() michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: AwaitIdleLocked(); michael@0: } michael@0: michael@0: void michael@0: MediaTaskQueue::AwaitIdleLocked() michael@0: { michael@0: mQueueMonitor.AssertCurrentThreadOwns(); michael@0: MOZ_ASSERT(mIsRunning || mTasks.empty()); michael@0: while (mIsRunning) { michael@0: mQueueMonitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaTaskQueue::Shutdown() michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: mIsShutdown = true; michael@0: AwaitIdleLocked(); michael@0: } michael@0: michael@0: void michael@0: MediaTaskQueue::Flush() michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: while (!mTasks.empty()) { michael@0: mTasks.pop(); michael@0: } michael@0: AwaitIdleLocked(); michael@0: } michael@0: michael@0: bool michael@0: MediaTaskQueue::IsEmpty() michael@0: { michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: return mTasks.empty(); michael@0: } michael@0: michael@0: bool michael@0: MediaTaskQueue::IsCurrentThreadIn() michael@0: { michael@0: #ifdef DEBUG michael@0: MonitorAutoLock mon(mQueueMonitor); michael@0: return NS_GetCurrentThread() == mRunningThread; michael@0: #else michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: MediaTaskQueue::Runner::Run() michael@0: { michael@0: RefPtr event; michael@0: { michael@0: MonitorAutoLock mon(mQueue->mQueueMonitor); michael@0: MOZ_ASSERT(mQueue->mIsRunning); michael@0: mQueue->mRunningThread = NS_GetCurrentThread(); michael@0: if (mQueue->mTasks.size() == 0) { michael@0: mQueue->mIsRunning = false; michael@0: mon.NotifyAll(); michael@0: return NS_OK; michael@0: } michael@0: event = mQueue->mTasks.front(); michael@0: mQueue->mTasks.pop(); michael@0: } michael@0: MOZ_ASSERT(event); michael@0: michael@0: // Note that dropping the queue monitor before running the task, and michael@0: // taking the monitor again after the task has run ensures we have memory michael@0: // fences enforced. This means that if the object we're calling wasn't michael@0: // designed to be threadsafe, it will be, provided we're only calling it michael@0: // in this task queue. michael@0: event->Run(); michael@0: michael@0: // Drop the reference to event. The event will hold a reference to the michael@0: // object it's calling, and we don't want to keep it alive, it may be michael@0: // making assumptions what holds references to it. This is especially michael@0: // the case if the object is waiting for us to shutdown, so that it michael@0: // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case). michael@0: event = nullptr; michael@0: michael@0: { michael@0: MonitorAutoLock mon(mQueue->mQueueMonitor); michael@0: if (mQueue->mTasks.size() == 0) { michael@0: // No more events to run. Exit the task runner. michael@0: mQueue->mIsRunning = false; michael@0: mon.NotifyAll(); michael@0: mQueue->mRunningThread = nullptr; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // There's at least one more event that we can run. Dispatch this Runner michael@0: // to the thread pool again to ensure it runs again. Note that we don't just michael@0: // run in a loop here so that we don't hog the thread pool. This means we may michael@0: // run on another thread next time, but we rely on the memory fences from michael@0: // mQueueMonitor for thread safety of non-threadsafe tasks. michael@0: { michael@0: MonitorAutoLock mon(mQueue->mQueueMonitor); michael@0: // Note: Hold the monitor *before* we dispatch, in case we context switch michael@0: // to another thread pool in the queue immediately and take the lock in the michael@0: // other thread; mRunningThread could be set to the new thread's value and michael@0: // then incorrectly anulled below in that case. michael@0: nsresult rv = mQueue->mPool->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: // Failed to dispatch, shutdown! michael@0: mQueue->mIsRunning = false; michael@0: mQueue->mIsShutdown = true; michael@0: mon.NotifyAll(); michael@0: } michael@0: mQueue->mRunningThread = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla