Bug 736342 - Add AsyncBlockWriter, which defers file writes to another thread. r=roc
authorChris Pearce <cpearce@mozilla.com>
Wed, 28 Mar 2012 13:04:20 +1300
changeset 90602 b564e3f1ffe0563dda0f32880f6ad99ce4e956a0
parent 90601 f2a1b3b8f3cef956832ad98fcb82d7b303ab36f5
child 90603 b604d4e1916d87373f06b4209d7f0cb672c4fa17
push id22366
push usermak77@bonardo.net
push dateThu, 29 Mar 2012 15:38:30 +0000
treeherdermozilla-central@ff3521bc6559 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs736342
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 736342 - Add AsyncBlockWriter, which defers file writes to another thread. r=roc
content/media/FileBlockCache.cpp
content/media/FileBlockCache.h
content/media/Makefile.in
content/media/VideoUtils.h
content/media/nsBuiltinDecoderStateMachine.cpp
new file mode 100644
--- /dev/null
+++ b/content/media/FileBlockCache.cpp
@@ -0,0 +1,342 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/XPCOM.h"
+#include "FileBlockCache.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+nsresult FileBlockCache::Open(PRFileDesc* aFD)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  NS_ENSURE_TRUE(aFD != nsnull, NS_ERROR_FAILURE);
+  {
+    MonitorAutoLock mon(mFileMonitor);
+    mFD = aFD;
+  }
+  {
+    MonitorAutoLock mon(mDataMonitor);
+    nsresult res = NS_NewThread(getter_AddRefs(mThread),
+                                nsnull,
+                                MEDIA_THREAD_STACK_SIZE);
+    mIsOpen = NS_SUCCEEDED(res);
+    return res;
+  }
+}
+
+FileBlockCache::FileBlockCache()
+  : mFileMonitor("nsMediaCache.Writer.IO.Monitor"),
+    mFD(nsnull),
+    mFDCurrentPos(0),
+    mDataMonitor("nsMediaCache.Writer.Data.Monitor"),
+    mIsWriteScheduled(false),
+    mIsOpen(false)
+{
+  MOZ_COUNT_CTOR(FileBlockCache);
+}
+
+FileBlockCache::~FileBlockCache()
+{
+  NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
+  {
+    // Note, mThread will be shutdown by the time this runs, so we won't
+    // block while taking mFileMonitor.
+    MonitorAutoLock mon(mFileMonitor);
+    if (mFD) {
+      PR_Close(mFD);
+      mFD = nsnull;
+    }
+  }
+  MOZ_COUNT_DTOR(FileBlockCache);
+}
+
+
+void FileBlockCache::Close()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  MonitorAutoLock mon(mDataMonitor);
+
+  mIsOpen = false;
+
+  if (mThread) {
+    // We must shut down the thread in another runnable. This is called
+    // while we're shutting down the media cache, and nsIThread::Shutdown()
+    // can cause events to run before it completes, which could end up
+    // opening more streams, while the media cache is shutting down and
+    // releasing memory etc! Also note we close mFD in the destructor so
+    // as to not disturb any IO that's currently running.
+    nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
+    mThread = nsnull;
+    NS_DispatchToMainThread(event);
+  }
+}
+
+nsresult FileBlockCache::WriteBlock(PRUint32 aBlockIndex, const PRUint8* aData)
+{
+  MonitorAutoLock mon(mDataMonitor);
+
+  if (!mIsOpen)
+    return NS_ERROR_FAILURE;
+
+  // Check if we've already got a pending write scheduled for this block.
+  mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
+  bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nsnull;
+  mBlockChanges[aBlockIndex] = new BlockChange(aData);
+
+  if (!blockAlreadyHadPendingChange || !mChangeIndexList.Contains(aBlockIndex)) {
+    // We either didn't already have a pending change for this block, or we
+    // did but we didn't have an entry for it in mChangeIndexList (we're in the process
+    // of writing it and have removed the block's index out of mChangeIndexList
+    // in Run() but not finished writing the block to file yet). Add the blocks
+    // index to the end of mChangeIndexList to ensure the block is written as
+    // as soon as possible.
+    mChangeIndexList.PushBack(aBlockIndex);
+  }
+  NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block");
+
+  EnsureWriteScheduled();
+
+  return NS_OK;
+}
+
+void FileBlockCache::EnsureWriteScheduled()
+{
+  mDataMonitor.AssertCurrentThreadOwns();
+
+  if (!mIsWriteScheduled) {
+    mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    mIsWriteScheduled = true;
+  }
+}
+
+nsresult FileBlockCache::Seek(PRInt64 aOffset)
+{
+  mFileMonitor.AssertCurrentThreadOwns();
+
+  if (mFDCurrentPos != aOffset) {
+    PROffset64 result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
+    if (result != aOffset) {
+      NS_WARNING("Failed to seek media cache file");
+      return NS_ERROR_FAILURE;
+    }
+    mFDCurrentPos = result;
+  }
+  return NS_OK;
+}
+
+nsresult FileBlockCache::ReadFromFile(PRInt32 aOffset,
+                                      PRUint8* aDest,
+                                      PRInt32 aBytesToRead,
+                                      PRInt32& aBytesRead)
+{
+  mFileMonitor.AssertCurrentThreadOwns();
+
+  nsresult res = Seek(aOffset);
+  if (NS_FAILED(res)) return res;
+  
+  aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
+  if (aBytesRead <= 0)
+    return NS_ERROR_FAILURE;
+  mFDCurrentPos += aBytesRead;
+  
+  return NS_OK;
+}
+
+nsresult FileBlockCache::WriteBlockToFile(PRInt32 aBlockIndex,
+                                          const PRUint8* aBlockData)
+{
+  mFileMonitor.AssertCurrentThreadOwns();
+
+  nsresult rv = Seek(aBlockIndex * BLOCK_SIZE);
+  if (NS_FAILED(rv)) return rv;
+
+  PRInt32 amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
+  if (amount < BLOCK_SIZE) {
+    NS_WARNING("Failed to write media cache block!");
+    return NS_ERROR_FAILURE;
+  }
+  mFDCurrentPos += BLOCK_SIZE;
+
+  return NS_OK;
+}
+
+nsresult FileBlockCache::MoveBlockInFile(PRInt32 aSourceBlockIndex,
+                                         PRInt32 aDestBlockIndex)
+{
+  mFileMonitor.AssertCurrentThreadOwns();
+
+  PRUint8 buf[BLOCK_SIZE];
+  PRInt32 bytesRead = 0;
+  if (NS_FAILED(ReadFromFile(aSourceBlockIndex * BLOCK_SIZE,
+                             buf,
+                             BLOCK_SIZE,
+                             bytesRead))) {
+    return NS_ERROR_FAILURE;
+  }
+  return WriteBlockToFile(aDestBlockIndex, buf);
+}
+
+nsresult FileBlockCache::Run()
+{
+  MonitorAutoLock mon(mDataMonitor);
+  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+  NS_ASSERTION(!mChangeIndexList.IsEmpty(), "Only dispatch when there's work to do");
+  NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
+
+  while (!mChangeIndexList.IsEmpty()) {
+    if (!mIsOpen) {
+      // We've been closed, abort, discarding unwritten changes.
+      mIsWriteScheduled = false;
+      return NS_ERROR_FAILURE;
+    }
+
+    // Process each pending change. We pop the index out of the change
+    // list, but leave the BlockChange in mBlockChanges until the change
+    // is written to file. This is so that any read which happens while
+    // we drop mDataMonitor to write will refer to the data's source in
+    // memory, rather than the not-yet up to date data written to file.
+    // This also ensures we will insert a new index into mChangeIndexList
+    // when this happens.
+
+    // Hold a reference to the change, in case another change
+    // overwrites the mBlockChanges entry for this block while we drop
+    // mDataMonitor to take mFileMonitor.
+    PRInt32 blockIndex = mChangeIndexList.PopFront();
+    nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
+    NS_ABORT_IF_FALSE(change,
+      "Change index list should only contain entries for blocks with changes");
+    {
+      MonitorAutoUnlock unlock(mDataMonitor);
+      MonitorAutoLock lock(mFileMonitor);
+      if (change->IsWrite()) {
+        WriteBlockToFile(blockIndex, change->mData.get());
+      } else if (change->IsMove()) {
+        MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
+      }
+    }
+    // If a new change has not been made to the block while we dropped
+    // mDataMonitor, clear reference to the old change. Otherwise, the old
+    // reference has been cleared already.
+    if (mBlockChanges[blockIndex] == change) {
+      mBlockChanges[blockIndex] = nsnull;
+    }
+  }
+
+  mIsWriteScheduled = false;
+
+  return NS_OK;
+}
+
+nsresult FileBlockCache::Read(PRInt64 aOffset,
+                              PRUint8* aData,
+                              PRInt32 aLength,
+                              PRInt32* aBytes)
+{
+  MonitorAutoLock mon(mDataMonitor);
+
+  if (!mFD || (aOffset / BLOCK_SIZE) > PR_INT32_MAX)
+    return NS_ERROR_FAILURE;
+
+  PRInt32 bytesToRead = aLength;
+  PRInt64 offset = aOffset;
+  PRUint8* dst = aData;
+  while (bytesToRead > 0) {
+    PRInt32 blockIndex = static_cast<PRInt32>(offset / BLOCK_SIZE);
+    PRInt32 start = offset % BLOCK_SIZE;
+    PRInt32 amount = NS_MIN(BLOCK_SIZE - start, bytesToRead);
+
+    // If the block is not yet written to file, we can just read from
+    // the memory buffer, otherwise we need to read from file.
+    PRInt32 bytesRead = 0;
+    nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
+    if (change && change->IsWrite()) {
+      // Block isn't yet written to file. Read from memory buffer.
+      const PRUint8* blockData = change->mData.get();
+      memcpy(dst, blockData + start, amount);
+      bytesRead = amount;
+    } else {
+      if (change && change->IsMove()) {
+        // The target block is the destination of a not-yet-completed move
+        // action, so read from the move's source block from file. Note we
+        // *don't* follow a chain of moves here, as a move's source index
+        // is resolved when MoveBlock() is called, and the move's source's
+        // block could be have itself been subject to a move (or write)
+        // which happened *after* this move was recorded.
+        blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
+      }
+      // Block has been written to file, either as the source block of a move,
+      // or as a stable (all changes made) block. Read the data directly
+      // from file.
+      nsresult res;
+      {
+        MonitorAutoUnlock unlock(mDataMonitor);
+        MonitorAutoLock lock(mFileMonitor);
+        res = ReadFromFile(blockIndex * BLOCK_SIZE + start,
+                           dst,
+                           amount,
+                           bytesRead);
+      }
+      NS_ENSURE_SUCCESS(res,res);
+    }
+    dst += bytesRead;
+    offset += bytesRead;
+    bytesToRead -= bytesRead;
+  }
+  *aBytes = aLength - bytesToRead;
+  return NS_OK;
+}
+
+nsresult FileBlockCache::MoveBlock(PRInt32 aSourceBlockIndex, PRInt32 aDestBlockIndex)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  MonitorAutoLock mon(mDataMonitor);
+
+  if (!mIsOpen)
+    return NS_ERROR_FAILURE;
+
+  mBlockChanges.EnsureLengthAtLeast(NS_MAX(aSourceBlockIndex, aDestBlockIndex) + 1);
+
+  // The source block's contents may be the destination of another pending
+  // move, which in turn can be the destination of another pending move,
+  // etc. Resolve the final source block, so that if one of the blocks in
+  // the chain of moves is overwritten, we don't lose the reference to the
+  // contents of the destination block.
+  PRInt32 sourceIndex = aSourceBlockIndex;
+  BlockChange* sourceBlock = nsnull;
+  while ((sourceBlock = mBlockChanges[sourceIndex]) &&
+          sourceBlock->IsMove()) {
+    sourceIndex = sourceBlock->mSourceBlockIndex;
+  }
+
+  if (mBlockChanges[aDestBlockIndex] == nsnull ||
+      !mChangeIndexList.Contains(aDestBlockIndex)) {
+    // Only add another entry to the change index list if we don't already
+    // have one for this block. We won't have an entry when either there's
+    // no pending change for this block, or if there is a pending change for
+    // this block and we're in the process of writing it (we've popped the
+    // block's index out of mChangeIndexList in Run() but not finished writing
+    // the block to file yet.
+    mChangeIndexList.PushBack(aDestBlockIndex);
+  }
+  
+  // If the source block hasn't yet been written to file then the dest block
+  // simply contains that same write. Resolve this as a write instead.
+  if (sourceBlock && sourceBlock->IsWrite()) {
+    mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
+  } else {
+    mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
+  }
+
+  EnsureWriteScheduled();
+
+  NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex),
+    "Should have scheduled block for change");
+
+  return NS_OK;
+}
+
+} // End namespace mozilla.
new file mode 100644
--- /dev/null
+++ b/content/media/FileBlockCache.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FILE_BLOCK_CACHE_H_
+#define FILE_BLOCK_CACHE_H_
+
+#include "mozilla/Monitor.h"
+#include "prio.h"
+#include "nsTArray.h"
+#include "nsMediaCache.h"
+#include "nsDeque.h"
+
+namespace mozilla {
+
+// Manages file I/O for the media cache. Data comes in over the network
+// via callbacks on the main thread, however we don't want to write the
+// incoming data to the media cache on the main thread, as this could block
+// causing UI jank. 
+//
+// So FileBlockCache provides an abstraction for a temporary file accessible
+// as an array of blocks, which supports a block move operation, and
+// allows synchronous reading and writing from any thread, with writes being
+// buffered so as not to block.
+//
+// Writes and cache block moves (which require reading) are deferred to
+// their own non-main thread. This object also ensures that data which has
+// been scheduled to be written, but hasn't actually *been* written, is read
+// as if it had, i.e. pending writes are cached in readable memory until
+// they're flushed to file.
+//
+// To improve efficiency, writes can only be done at block granularity,
+// whereas reads can be done with byte granularity.
+//
+// Note it's also recommended not to read from the media cache from the main
+// thread to prevent jank.
+//
+// When WriteBlock() or MoveBlock() are called, data about how to complete
+// the block change is added to mBlockChanges, indexed by block index, and
+// the block index is appended to the mChangeIndexList. This enables
+// us to quickly tell if a block has been changed, and ensures we can perform
+// the changes in the correct order. An event is dispatched to perform the
+// changes listed in mBlockChanges to file. Read() checks mBlockChanges and
+// determines the current data to return, reading from file or from
+// mBlockChanges as necessary.
+class FileBlockCache : public nsRunnable {
+public:
+  enum {
+    BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
+  };
+
+  FileBlockCache();
+
+  ~FileBlockCache();
+
+  // Assumes ownership of aFD.
+  nsresult Open(PRFileDesc* aFD);
+
+  // Closes writer, shuts down thread.
+  void Close();
+
+  // Can be called on any thread. This defers to a non-main thread.
+  nsresult WriteBlock(PRUint32 aBlockIndex, const PRUint8* aData);
+
+  // Performs block writes and block moves on its own thread.
+  NS_IMETHOD Run();
+
+  // Synchronously reads data from file. May read from file or memory
+  // depending on whether written blocks have been flushed to file yet.
+  // Not recommended to be called from the main thread, as can cause jank.
+  nsresult Read(PRInt64 aOffset,
+                PRUint8* aData,
+                PRInt32 aLength,
+                PRInt32* aBytes);
+
+  // Moves a block asynchronously. Can be called on any thread.
+  // This defers file I/O to a non-main thread.
+  nsresult MoveBlock(PRInt32 aSourceBlockIndex, PRInt32 aDestBlockIndex);
+
+  // Represents a change yet to be made to a block in the file. The change
+  // is either a write (and the data to be written is stored in this struct)
+  // or a move (and the index of the source block is stored instead).
+  struct BlockChange {
+
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange)
+
+    // This block is waiting in memory to be written.
+    // Stores a copy of the block, so we can write it asynchronously.
+    BlockChange(const PRUint8* aData)
+      : mSourceBlockIndex(-1)
+    {
+      mData = new PRUint8[BLOCK_SIZE];
+      memcpy(mData.get(), aData, BLOCK_SIZE);
+    }
+
+    // This block's contents are located in another file
+    // block, i.e. this block has been moved.
+    BlockChange(PRInt32 aSourceBlockIndex)
+      : mSourceBlockIndex(aSourceBlockIndex) {}
+
+    nsAutoArrayPtr<PRUint8> mData;
+    const PRInt32 mSourceBlockIndex;
+
+    bool IsMove() const {
+      return mSourceBlockIndex != -1;
+    }
+    bool IsWrite() const {
+      return mSourceBlockIndex == -1 &&
+             mData.get() != nsnull;
+    }
+  };
+
+  class Int32Queue : private nsDeque {
+  public:
+    PRInt32 PopFront() {
+      PRInt32 front = ObjectAt(0);
+      nsDeque::PopFront();
+      return front;
+    }
+
+    void PushBack(PRInt32 aValue) {
+      nsDeque::Push(reinterpret_cast<void*>(aValue));
+    }
+
+    bool Contains(PRInt32 aValue) {
+      for (PRInt32 i = 0; i < GetSize(); ++i) {
+        if (ObjectAt(i) == aValue) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    bool IsEmpty() {
+      return nsDeque::GetSize() == 0;
+    }
+
+  private:
+    PRInt32 ObjectAt(PRInt32 aIndex) {
+      void* v = nsDeque::ObjectAt(aIndex);
+      // Ugly hack to work around "casting 64bit void* to 32bit int loses precision"
+      // error on 64bit Linux.
+      return *(reinterpret_cast<PRInt32*>(&v));
+    }
+  };
+
+private:
+  // Monitor which controls access to mFD and mFDCurrentPos. Don't hold
+  // mDataMonitor while holding mFileMonitor! mFileMonitor must be owned
+  // while accessing any of the following data fields or methods.
+  mozilla::Monitor mFileMonitor;
+  // Moves a block already committed to file.
+  nsresult MoveBlockInFile(PRInt32 aSourceBlockIndex,
+                           PRInt32 aDestBlockIndex);
+  // Seeks file pointer.
+  nsresult Seek(PRInt64 aOffset);
+  // Reads data from file offset.
+  nsresult ReadFromFile(PRInt32 aOffset,
+                        PRUint8* aDest,
+                        PRInt32 aBytesToRead,
+                        PRInt32& aBytesRead);
+  nsresult WriteBlockToFile(PRInt32 aBlockIndex, const PRUint8* aBlockData);
+  // File descriptor we're writing to. This is created externally, but
+  // shutdown by us.
+  PRFileDesc* mFD;
+  // The current file offset in the file.
+  PRInt64 mFDCurrentPos;
+
+  // Monitor which controls access to all data in this class, except mFD
+  // and mFDCurrentPos. Don't hold mDataMonitor while holding mFileMonitor!
+  // mDataMonitor must be owned while accessing any of the following data
+  // fields or methods.
+  mozilla::Monitor mDataMonitor;
+  // Ensures we either are running the event to preform IO, or an event
+  // has been dispatched to preform the IO.
+  // mDataMonitor must be owned while calling this.
+  void EnsureWriteScheduled();
+  // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nsnull,
+  // then the block has no pending changes to be written, but if
+  // mBlockChanges[offset/BLOCK_SIZE] != nsnull, then either there's a block
+  // cached in memory waiting to be written, or this block is the target of a
+  // block move.
+  nsTArray< nsRefPtr<BlockChange> > mBlockChanges;
+  // Thread upon which block writes and block moves are performed. This is
+  // created upon open, and shutdown (asynchronously) upon close (on the
+  // main thread).
+  nsCOMPtr<nsIThread> mThread;
+  // Queue of pending block indexes that need to be written or moved.
+  //nsAutoTArray<PRInt32, 8> mChangeIndexList;
+  Int32Queue mChangeIndexList;
+  // True if we've dispatched an event to commit all pending block changes
+  // to file on mThread.
+  bool mIsWriteScheduled;
+  // True if the writer is ready to write data to file.
+  bool mIsOpen;
+};
+
+} // End namespace mozilla.
+
+#endif /* FILE_BLOCK_CACHE_H_ */
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -41,28 +41,30 @@ VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = content
 LIBRARY_NAME = gkconmedia_s
 LIBXUL_LIBRARY = 1
 
 EXPORTS = \
+  FileBlockCache.h \
   nsAudioAvailableEventManager.h \
   nsMediaDecoder.h \
   nsMediaCache.h \
   nsBuiltinDecoder.h \
   nsBuiltinDecoderStateMachine.h \
   nsBuiltinDecoderReader.h \
   MediaResource.h \
   VideoFrameContainer.h \
   VideoUtils.h \
   $(NULL)
 
 CPPSRCS = \
+  FileBlockCache.cpp \
   nsAudioAvailableEventManager.cpp \
   nsMediaDecoder.cpp \
   nsMediaCache.cpp \
   nsBuiltinDecoder.cpp \
   nsBuiltinDecoderStateMachine.cpp \
   nsBuiltinDecoderReader.cpp \
   MediaResource.cpp \
   VideoFrameContainer.cpp \
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -38,16 +38,17 @@
 
 #ifndef VideoUtils_h
 #define VideoUtils_h
 
 #include "mozilla/ReentrantMonitor.h"
 
 #include "nsRect.h"
 #include "nsIThreadManager.h"
+#include "nsThreadUtils.h"
 
 #include "CheckedInt.h"
 
 using mozilla::CheckedInt64;
 using mozilla::CheckedUint64;
 using mozilla::CheckedInt32;
 using mozilla::CheckedUint32;
 
@@ -97,16 +98,31 @@ private:
     ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&);
     ReentrantMonitorAutoExit& operator =(const ReentrantMonitorAutoExit&);
     static void* operator new(size_t) CPP_THROW_NEW;
     static void operator delete(void*);
 
     ReentrantMonitor* mReentrantMonitor;
 };
 
+// Shuts down a thread asynchronously.
+class ShutdownThreadEvent : public nsRunnable 
+{
+public:
+  ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
+  ~ShutdownThreadEvent() {}
+  NS_IMETHOD Run() {
+    mThread->Shutdown();
+    mThread = nsnull;
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<nsIThread> mThread;
+};
+
 } // namespace mozilla
 
 // Converts from number of audio frames (aFrames) to microseconds, given
 // the specified audio rate (aRate). Stores result in aOutUsecs. Returns true
 // if the operation succeeded, or false if there was an integer overflow
 // while calulating the conversion.
 CheckedInt64 FramesToUsecs(PRInt64 aFrames, PRUint32 aRate);
 
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -164,31 +164,16 @@ public:
     mDecoder->MetadataLoaded(mChannels, mRate);
     return NS_OK;
   }
 
   const PRUint32 mChannels;
   const PRUint32 mRate;
 };
 
-// Shuts down a thread asynchronously.
-class ShutdownThreadEvent : public nsRunnable 
-{
-public:
-  ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
-  ~ShutdownThreadEvent() {}
-  NS_IMETHOD Run() {
-    mThread->Shutdown();
-    mThread = nsnull;
-    return NS_OK;
-  }
-private:
-  nsCOMPtr<nsIThread> mThread;
-};
-
 // Owns the global state machine thread and counts of
 // state machine and decoder threads. There should
 // only be one instance of this class.
 class StateMachineTracker
 {
 private:
   StateMachineTracker() :
     mMonitor("media.statemachinetracker"),