Bug 913653: Remove lock from IOInterposer and add IOInterposer thread registration; r=froydnj
authorAaron Klotz <aklotz@mozilla.com>
Tue, 08 Apr 2014 22:57:52 -0600
changeset 197214 179b22b687c1b698c77b46bbadfe0f8a4f2ecba5
parent 197213 26d87e24848b20d820fef964ffeecdbffb6a7cc8
child 197215 c4979703f76a6da083a62e8952f98994d4504ca8
child 197371 7fa9564a24864d192383b550c2db6ebcb1b3ad28
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs913653
milestone31.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 913653: Remove lock from IOInterposer and add IOInterposer thread registration; r=froydnj
dom/src/storage/DOMStorageDBThread.cpp
ipc/chromium/src/base/thread.cc
netwerk/cache2/CacheIOThread.cpp
startupcache/StartupCache.cpp
xpcom/build/IOInterposer.cpp
xpcom/build/IOInterposer.h
xpcom/threads/nsThread.cpp
--- a/dom/src/storage/DOMStorageDBThread.cpp
+++ b/dom/src/storage/DOMStorageDBThread.cpp
@@ -15,16 +15,17 @@
 #include "mozStorageHelper.h"
 #include "mozIStorageService.h"
 #include "mozIStorageBindingParamsArray.h"
 #include "mozIStorageBindingParams.h"
 #include "mozIStorageValueArray.h"
 #include "mozIStorageFunction.h"
 #include "nsIObserverService.h"
 #include "nsIVariant.h"
+#include "mozilla/IOInterposer.h"
 #include "mozilla/Services.h"
 
 // How long we collect write oprerations
 // before they are flushed to the database
 // In milliseconds.
 #define FLUSHING_INTERVAL_MS 5000
 
 // Write Ahead Log's maximum size is 512KB
@@ -274,19 +275,21 @@ DOMStorageDBThread::SetDefaultPriority()
     PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
   }
 }
 
 void
 DOMStorageDBThread::ThreadFunc(void* aArg)
 {
   PR_SetCurrentThreadName("localStorage DB");
+  mozilla::IOInterposer::RegisterCurrentThread();
 
   DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
   thread->ThreadFunc();
+  mozilla::IOInterposer::UnregisterCurrentThread();
 }
 
 void
 DOMStorageDBThread::ThreadFunc()
 {
   nsresult rv = InitDatabase();
 
   MonitorAutoLock lockMonitor(mMonitor);
--- a/ipc/chromium/src/base/thread.cc
+++ b/ipc/chromium/src/base/thread.cc
@@ -4,16 +4,17 @@
 
 #include "base/thread.h"
 
 #include "base/lazy_instance.h"
 #include "base/string_util.h"
 #include "base/thread_local.h"
 #include "base/waitable_event.h"
 #include "GeckoProfiler.h"
+#include "mozilla/IOInterposer.h"
 
 namespace base {
 
 // This task is used to trigger the message loop to exit.
 class ThreadQuitTask : public Task {
  public:
   virtual void Run() {
     MessageLoop::current()->Quit();
@@ -134,16 +135,17 @@ void Thread::StopSoon() {
   DCHECK(message_loop_);
 
   message_loop_->PostTask(FROM_HERE, new ThreadQuitTask());
 }
 
 void Thread::ThreadMain() {
   char aLocal;
   profiler_register_thread(name_.c_str(), &aLocal);
+  mozilla::IOInterposer::RegisterCurrentThread();
 
   // The message loop for this thread.
   MessageLoop message_loop(startup_data_->options.message_loop_type);
 
   // Complete the initialization of our Thread object.
   thread_id_ = PlatformThread::CurrentId();
   PlatformThread::SetName(name_.c_str());
   message_loop.set_thread_name(name_);
@@ -162,16 +164,17 @@ void Thread::ThreadMain() {
   message_loop.Run();
 
   // Let the thread do extra cleanup.
   CleanUp();
 
   // Assert that MessageLoop::Quit was called by ThreadQuitTask.
   DCHECK(GetThreadWasQuitProperly());
 
+  mozilla::IOInterposer::UnregisterCurrentThread();
   profiler_unregister_thread();
 
   // We can't receive messages anymore.
   message_loop_ = NULL;
   thread_id_ = 0;
 }
 
 }  // namespace base
--- a/netwerk/cache2/CacheIOThread.cpp
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -4,16 +4,17 @@
 
 #include "CacheIOThread.h"
 #include "CacheFileIOManager.h"
 
 #include "nsIRunnable.h"
 #include "nsISupportsImpl.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
+#include "mozilla/IOInterposer.h"
 #include "mozilla/VisualEventTracer.h"
 
 namespace mozilla {
 namespace net {
 
 CacheIOThread* CacheIOThread::sSelf = nullptr;
 
 NS_IMPL_ISUPPORTS1(CacheIOThread, nsIThreadObserver)
@@ -147,18 +148,20 @@ already_AddRefed<nsIEventTarget> CacheIO
 
   return target.forget();
 }
 
 // static
 void CacheIOThread::ThreadFunc(void* aClosure)
 {
   PR_SetCurrentThreadName("Cache2 I/O");
+  mozilla::IOInterposer::RegisterCurrentThread();
   CacheIOThread* thread = static_cast<CacheIOThread*>(aClosure);
   thread->ThreadFunc();
+  mozilla::IOInterposer::UnregisterCurrentThread();
 }
 
 void CacheIOThread::ThreadFunc()
 {
   nsCOMPtr<nsIThreadInternal> threadInternal;
 
   {
     MonitorAutoLock lock(mMonitor);
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "prio.h"
 #include "pldhash.h"
 #include "nsXPCOMStrings.h"
+#include "mozilla/IOInterposer.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/scache/StartupCache.h"
 
 #include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIClassInfo.h"
@@ -531,25 +532,27 @@ StartupCache::WaitOnWriteThread()
   PR_JoinThread(mWriteThread);
   mWriteThread = nullptr;
 }
 
 void
 StartupCache::ThreadedWrite(void *aClosure)
 {
   PR_SetCurrentThreadName("StartupCache");
+  mozilla::IOInterposer::RegisterCurrentThread();
   /*
    * It is safe to use the pointer passed in aClosure to reference the
    * StartupCache object because the thread's lifetime is tightly coupled to
    * the lifetime of the StartupCache object; this thread is joined in the
    * StartupCache destructor, guaranteeing that this function runs if and only
    * if the StartupCache object is valid.
    */
   StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure);
   startupCacheObj->WriteToDisk();
+  mozilla::IOInterposer::UnregisterCurrentThread();
 }
 
 /*
  * The write-thread is spawned on a timeout(which is reset with every write). This
  * can avoid a slow shutdown. After writing out the cache, the zipreader is
  * reloaded on the worker thread.
  */
 void
--- a/xpcom/build/IOInterposer.cpp
+++ b/xpcom/build/IOInterposer.cpp
@@ -4,56 +4,69 @@
 
 #include <algorithm>
 #include <vector>
 
 #include "IOInterposer.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Mutex.h"
+#if defined(MOZILLA_INTERNAL_API)
+// We need to undefine MOZILLA_INTERNAL_API for RefPtr.h because IOInterposer
+// does not clean up its data before shutdown.
+#undef MOZILLA_INTERNAL_API
+#include "mozilla/RefPtr.h"
+#define MOZILLA_INTERNAL_API
+#else
+#include "mozilla/RefPtr.h"
+#endif // defined(MOZILLA_INTERNAL_API)
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ThreadLocal.h"
 #if !defined(XP_WIN)
 #include "NSPRInterposer.h"
 #endif // !defined(XP_WIN)
 #include "nsXULAppAPI.h"
 #include "PoisonIOInterposer.h"
 
 using namespace mozilla;
 
 namespace {
 
+/** Find if a vector contains a specific element */
+template<class T>
+bool VectorContains(const std::vector<T>& vector, const T& element)
+{
+  return std::find(vector.begin(), vector.end(), element) != vector.end();
+}
+
+/** Remove element from a vector */
+template<class T>
+void VectorRemove(std::vector<T>& vector, const T& element)
+{
+  typename std::vector<T>::iterator newEnd = std::remove(vector.begin(),
+                                                         vector.end(), element);
+  vector.erase(newEnd, vector.end());
+}
+
 /** Lists of Observers */
-struct ObserverLists {
+struct ObserverLists : public AtomicRefCounted<ObserverLists>
+{
   ObserverLists()
-    : mObserverListsLock(PR_NewLock())
-    , mIsEnabled(true)
   {
-    // We don't do MOZ_COUNT_CTOR(ObserverLists) as we will need to leak the
-    // IO interposer when doing late-write checks, which uses IO interposing
-    // to check for writes while static destructors are invoked.
   }
 
-  // mObserverListsLock guards access to lists of observers
-  // Note, we can use mozilla::Mutex here as the ObserverLists may be leaked,
-  // as we want to monitor IO during shutdown. Furthermore, as we may have to
-  // unregister observers during shutdown an OffTheBooksMutex is not an option
-  // either, as it base calls into sDeadlockDetector which may be nullptr
-  // during shutdown.
-  PRLock* mObserverListsLock;
-
-  // Used for quickly disabling everything by IOInterposer::Disable()
-  mozilla::Atomic<bool>              mIsEnabled;
-
-  ~ObserverLists()
+  ObserverLists(ObserverLists const & aOther)
+    : mCreateObservers(aOther.mCreateObservers)
+    , mReadObservers(aOther.mReadObservers)
+    , mWriteObservers(aOther.mWriteObservers)
+    , mFSyncObservers(aOther.mFSyncObservers)
+    , mStatObservers(aOther.mStatObservers)
+    , mCloseObservers(aOther.mCloseObservers)
   {
-    PR_DestroyLock(mObserverListsLock);
-    mObserverListsLock = nullptr;
   }
-
   // Lists of observers for read, write and fsync events respectively
   // These are implemented as vectors since they are allowed to survive gecko,
   // without reporting leaks. This is necessary for the IOInterposer to be used
   // for late-write checks.
   std::vector<IOInterposeObserver*>  mCreateObservers;
   std::vector<IOInterposeObserver*>  mReadObservers;
   std::vector<IOInterposeObserver*>  mWriteObservers;
   std::vector<IOInterposeObserver*>  mFSyncObservers;
@@ -74,36 +87,281 @@ public:
     PR_Lock(aLock);
   }
   ~AutoPRLock()
   {
     PR_Unlock(mLock);
   }
 };
 
-// List of observers registered
-static StaticAutoPtr<ObserverLists> sObserverLists;
-static ThreadLocal<bool> sIsMainThread;
+class PerThreadData
+{
+public:
+  PerThreadData(bool aIsMainThread = false)
+    : mIsMainThread(aIsMainThread)
+    , mIsHandlingObservation(false)
+    , mCurrentGeneration(0)
+  {
+  }
+
+  void
+  CallObservers(IOInterposeObserver::Observation& aObservation)
+  {
+    // Prevent recursive reporting.
+    if (mIsHandlingObservation) {
+      return;
+    }
 
-/** Find if a vector contains a specific element */
-template<class T>
-bool VectorContains(const std::vector<T>& vector, const T& element)
+    mIsHandlingObservation = true;
+    // Decide which list of observers to inform
+    std::vector<IOInterposeObserver*>* observers = nullptr;
+    switch (aObservation.ObservedOperation()) {
+      case IOInterposeObserver::OpCreateOrOpen:
+        {
+          observers = &mObserverLists->mCreateObservers;
+        }
+        break;
+      case IOInterposeObserver::OpRead:
+        {
+          observers = &mObserverLists->mReadObservers;
+        }
+        break;
+      case IOInterposeObserver::OpWrite:
+        {
+          observers = &mObserverLists->mWriteObservers;
+        }
+        break;
+      case IOInterposeObserver::OpFSync:
+        {
+          observers = &mObserverLists->mFSyncObservers;
+        }
+        break;
+      case IOInterposeObserver::OpStat:
+        {
+          observers = &mObserverLists->mStatObservers;
+        }
+        break;
+      case IOInterposeObserver::OpClose:
+        {
+          observers = &mObserverLists->mCloseObservers;
+        }
+        break;
+      default:
+        {
+          // Invalid IO operation, see documentation comment for
+          // IOInterposer::Report()
+          MOZ_ASSERT(false);
+          // Just ignore it in non-debug builds.
+          return;
+        }
+    }
+    MOZ_ASSERT(observers);
+
+    // Inform observers
+    for (std::vector<IOInterposeObserver*>::iterator i = observers->begin(),
+         e = observers->end(); i != e; ++i)
+    {
+      (*i)->Observe(aObservation);
+    }
+    mIsHandlingObservation = false;
+  }
+
+  inline uint32_t
+  GetCurrentGeneration() const
+  {
+    return mCurrentGeneration;
+  }
+
+  inline bool
+  IsMainThread() const
+  {
+    return mIsMainThread;
+  }
+
+  inline void
+  SetObserverLists(uint32_t aNewGeneration, RefPtr<ObserverLists>& aNewLists)
+  {
+    mCurrentGeneration = aNewGeneration;
+    mObserverLists = aNewLists;
+  }
+
+private:
+  bool                  mIsMainThread;
+  bool                  mIsHandlingObservation;
+  uint32_t              mCurrentGeneration;
+  RefPtr<ObserverLists> mObserverLists;
+};
+
+class MasterList
 {
-  return std::find(vector.begin(), vector.end(), element) != vector.end();
-}
+public:
+  MasterList()
+    : mLock(PR_NewLock())
+    , mObservedOperations(IOInterposeObserver::OpNone)
+    , mIsEnabled(true)
+  {
+  }
+
+  ~MasterList()
+  {
+    PR_DestroyLock(mLock);
+    mLock = nullptr;
+  }
+
+  inline void
+  Disable()
+  {
+    mIsEnabled = false;
+  }
+
+  void
+  Register(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver)
+  {
+    AutoPRLock lock(mLock);
+
+    ObserverLists* newLists = nullptr;
+    if (mObserverLists) {
+      newLists = new ObserverLists(*mObserverLists);
+    } else {
+      newLists = new ObserverLists();
+    }
+    // You can register to observe multiple types of observations
+    // but you'll never be registered twice for the same observations.
+    if (aOp & IOInterposeObserver::OpCreateOrOpen &&
+        !VectorContains(newLists->mCreateObservers, aObserver)) {
+      newLists->mCreateObservers.push_back(aObserver);
+    }
+    if (aOp & IOInterposeObserver::OpRead &&
+        !VectorContains(newLists->mReadObservers, aObserver)) {
+      newLists->mReadObservers.push_back(aObserver);
+    }
+    if (aOp & IOInterposeObserver::OpWrite &&
+        !VectorContains(newLists->mWriteObservers, aObserver)) {
+      newLists->mWriteObservers.push_back(aObserver);
+    }
+    if (aOp & IOInterposeObserver::OpFSync &&
+        !VectorContains(newLists->mFSyncObservers, aObserver)) {
+      newLists->mFSyncObservers.push_back(aObserver);
+    }
+    if (aOp & IOInterposeObserver::OpStat &&
+        !VectorContains(newLists->mStatObservers, aObserver)) {
+      newLists->mStatObservers.push_back(aObserver);
+    }
+    if (aOp & IOInterposeObserver::OpClose &&
+        !VectorContains(newLists->mCloseObservers, aObserver)) {
+      newLists->mCloseObservers.push_back(aObserver);
+    }
+    mObserverLists = newLists;
+    mObservedOperations = (IOInterposeObserver::Operation)
+                            (mObservedOperations | aOp);
+
+    mCurrentGeneration++;
+  }
+
+  void
+  Unregister(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver)
+  {
+    AutoPRLock lock(mLock);
+
+    ObserverLists* newLists = nullptr;
+    if (mObserverLists) {
+      newLists = new ObserverLists(*mObserverLists);
+    } else {
+      newLists = new ObserverLists();
+    }
 
-/** Remove element from a vector */
-template<class T>
-void VectorRemove(std::vector<T>& vector, const T& element)
-{
-  typename std::vector<T>::iterator newEnd = std::remove(vector.begin(),
-                                                         vector.end(), element);
-  vector.erase(newEnd, vector.end());
-}
+    if (aOp & IOInterposeObserver::OpCreateOrOpen) {
+      VectorRemove(newLists->mCreateObservers, aObserver);
+      if (newLists->mCreateObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations &
+                          ~IOInterposeObserver::OpCreateOrOpen);
+      }
+    }
+    if (aOp & IOInterposeObserver::OpRead) {
+      VectorRemove(newLists->mReadObservers, aObserver);
+      if (newLists->mReadObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpRead);
+      }
+    }
+    if (aOp & IOInterposeObserver::OpWrite) {
+      VectorRemove(newLists->mWriteObservers, aObserver);
+      if (newLists->mWriteObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpWrite);
+      }
+    }
+    if (aOp & IOInterposeObserver::OpFSync) {
+      VectorRemove(newLists->mFSyncObservers, aObserver);
+      if (newLists->mFSyncObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpFSync);
+      }
+    }
+    if (aOp & IOInterposeObserver::OpStat) {
+      VectorRemove(newLists->mStatObservers, aObserver);
+      if (newLists->mStatObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpStat);
+      }
+    }
+    if (aOp & IOInterposeObserver::OpClose) {
+      VectorRemove(newLists->mCloseObservers, aObserver);
+      if (newLists->mCloseObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpClose);
+      }
+    }
+    mObserverLists = newLists;
+    mCurrentGeneration++;
+  }
+ 
+  void
+  Update(PerThreadData &aPtd)
+  {
+    if (mCurrentGeneration == aPtd.GetCurrentGeneration()) {
+      return;
+    }
+    // If the generation counts don't match then we need to update the current
+    // thread's observer list with the new master list.
+    AutoPRLock lock(mLock);
+    aPtd.SetObserverLists(mCurrentGeneration, mObserverLists);
+  }
 
+  inline bool
+  IsObservedOperation(IOInterposeObserver::Operation aOp)
+  {
+    // The quick reader may observe that no locks are being employed here,
+    // hence the result of the operations is truly undefined. However, most
+    // computers will usually return either true or false, which is good enough.
+    // It is not a problem if we occasionally report more or less IO than is
+    // actually occurring.
+    return mIsEnabled && !!(mObservedOperations & aOp);
+  }
+
+private:
+  RefPtr<ObserverLists>             mObserverLists;
+  // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked
+  // (We want to monitor IO during shutdown). Furthermore, as we may have to
+  // unregister observers during shutdown an OffTheBooksMutex is not an option
+  // either, as its base calls into sDeadlockDetector which may be nullptr
+  // during shutdown.
+  PRLock*                           mLock;
+  // Flags tracking which operations are being observed
+  IOInterposeObserver::Operation    mObservedOperations;
+  // Used for quickly disabling everything by IOInterposer::Disable()
+  Atomic<bool>                      mIsEnabled;
+  // Used to inform threads that the master observer list has changed
+  Atomic<uint32_t>                  mCurrentGeneration;
+};
+
+// List of observers registered
+static StaticAutoPtr<MasterList> sMasterList;
+static ThreadLocal<PerThreadData*> sThreadLocalData;
 } // anonymous namespace
 
 IOInterposeObserver::Observation::Observation(Operation aOperation,
                                               const char* aReference,
                                               bool aShouldReport)
   : mOperation(aOperation)
   , mReference(aReference)
   , mShouldReport(IOInterposer::IsObservedOperation(aOperation) &&
@@ -130,258 +388,142 @@ void
 IOInterposeObserver::Observation::Report()
 {
   if (mShouldReport) {
     mEnd = TimeStamp::Now();
     IOInterposer::Report(*this);
   }
 }
 
-// Flags tracking which operations are being observed
-IOInterposeObserver::Operation IOInterposer::sObservedOperations =
-                                                  IOInterposeObserver::OpNone;
-
-/* static */ void IOInterposer::Init()
+/* static */ bool
+IOInterposer::Init()
 {
   // Don't initialize twice...
-  if (sObserverLists) {
-    return;
+  if (sMasterList) {
+    return true;
   }
-  sObserverLists = new ObserverLists();
-  sObservedOperations = IOInterposeObserver::OpNone;
-  if (sIsMainThread.init()) {
+  if (!sThreadLocalData.init()) {
+    return false;
+  }
 #if defined(XP_WIN)
-    bool isMainThread = XRE_GetWindowsEnvironment() !=
-                          WindowsEnvironmentType_Metro;
+  bool isMainThread = XRE_GetWindowsEnvironment() !=
+                        WindowsEnvironmentType_Metro;
 #else
-    bool isMainThread = true;
+  bool isMainThread = true;
 #endif
-    sIsMainThread.set(isMainThread);
-  }
+  RegisterCurrentThread(isMainThread);
+  sMasterList = new MasterList();
+
   // Now we initialize the various interposers depending on platform
   InitPoisonIOInterposer();
   // We don't hook NSPR on Windows because PoisonIOInterposer captures a
   // superset of the former's events.
 #if !defined(XP_WIN)
   InitNSPRIOInterposing();
 #endif
+  return true;
 }
 
 /* static */ bool
 IOInterposeObserver::IsMainThread()
 {
-  return sIsMainThread.initialized() && sIsMainThread.get();
+  if (!sThreadLocalData.initialized()) {
+    return false;
+  }
+  PerThreadData *ptd = sThreadLocalData.get();
+  if (!ptd) {
+    return false;
+  }
+  return ptd->IsMainThread();
 }
 
-/* static */ void IOInterposer::Clear()
+/* static */ void
+IOInterposer::Clear()
 {
-  // Clear() shouldn't be called if Init() wasn't called,
-  MOZ_ASSERT(sObserverLists);
-  if (sObserverLists) {
-    // We require everybody unregister before clearing. If somebody didn't then
-    // this is probably a case where one consumer clears the IO interposer and
-    // another consumer still wants events.
-    MOZ_ASSERT(sObserverLists->mReadObservers.empty());
-    MOZ_ASSERT(sObserverLists->mWriteObservers.empty());
-    MOZ_ASSERT(sObserverLists->mFSyncObservers.empty());
-
-    sObserverLists = nullptr;
-    sObservedOperations = IOInterposeObserver::OpNone;
-  }
+  sMasterList = nullptr;
 }
 
 /* static */ void
 IOInterposer::Disable()
 {
-  if (!sObserverLists) {
+  if (!sMasterList) {
     return;
   }
-  sObserverLists->mIsEnabled = false;
+  sMasterList->Disable();
 }
 
-/* static */ void IOInterposer::Report(
-  IOInterposeObserver::Observation& aObservation)
+/* static */ void
+IOInterposer::Report(IOInterposeObserver::Observation& aObservation)
 {
-  // IOInterposer::Init most be called before this method
-  MOZ_ASSERT(sObserverLists);
-  if (!sObserverLists) {
+  MOZ_ASSERT(sMasterList);
+  if (!sMasterList) {
     return;
   }
 
-  //TODO: We only need read access here, so we should investigate the
-  //      performance overhead involved in using some kind of shared lock.
-  //      Work towards this end is tracked in bug #913653
-  AutoPRLock listLock(sObserverLists->mObserverListsLock);
+  PerThreadData* ptd = sThreadLocalData.get();
+  if (!ptd) {
+    // In this case the current thread is not registered with IOInterposer.
+    // Alternatively we could take the slow path and just lock everything if
+    // we're not registered. That could potentially perform poorly, though.
+    return;
+  }
+  sMasterList->Update(*ptd);
 
-  // Don't try to report if there's nobody listening
+  // Don't try to report if there's nobody listening.
   if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) {
     return;
   }
 
-  // Decide which list of observers to inform
-  std::vector<IOInterposeObserver*>* observers = nullptr;
-  switch (aObservation.ObservedOperation()) {
-    case IOInterposeObserver::OpCreateOrOpen:
-      {
-        observers = &sObserverLists->mCreateObservers;
-      }
-      break;
-    case IOInterposeObserver::OpRead:
-      {
-        observers = &sObserverLists->mReadObservers;
-      }
-      break;
-    case IOInterposeObserver::OpWrite:
-      {
-        observers = &sObserverLists->mWriteObservers;
-      }
-      break;
-    case IOInterposeObserver::OpFSync:
-      {
-        observers = &sObserverLists->mFSyncObservers;
-      }
-      break;
-    case IOInterposeObserver::OpStat:
-      {
-        observers = &sObserverLists->mStatObservers;
-      }
-      break;
-    case IOInterposeObserver::OpClose:
-      {
-        observers = &sObserverLists->mCloseObservers;
-      }
-      break;
-    default:
-      {
-        // Invalid IO operation, see documentation comment for Report()
-        MOZ_ASSERT(false);
-        // Just ignore is in non-debug builds.
-        return;
-      }
-  }
-  MOZ_ASSERT(observers);
-
-  // Inform observers
-  uint32_t nObservers = observers->size();
-  for (uint32_t i = 0; i < nObservers; ++i) {
-    (*observers)[i]->Observe(aObservation);
-  }
+  ptd->CallObservers(aObservation);
 }
 
 /* static */ bool
 IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp)
 {
-  return sObserverLists && sObserverLists->mIsEnabled &&
-         !!(sObservedOperations & aOp);
+  return sMasterList && sMasterList->IsObservedOperation(aOp);
 }
 
-/* static */ void IOInterposer::Register(IOInterposeObserver::Operation aOp,
-                                         IOInterposeObserver* aObserver)
+/* static */ void
+IOInterposer::Register(IOInterposeObserver::Operation aOp,
+                       IOInterposeObserver* aObserver)
 {
-  // We should never register nullptr as observer
   MOZ_ASSERT(aObserver);
-  if (!sObserverLists || !aObserver) {
+  if (!sMasterList || !aObserver) {
     return;
   }
 
-  AutoPRLock listLock(sObserverLists->mObserverListsLock);
-
-  // You can register to observe multiple types of observations
-  // but you'll never be registered twice for the same observations.
-  if (aOp & IOInterposeObserver::OpCreateOrOpen &&
-      !VectorContains(sObserverLists->mCreateObservers, aObserver)) {
-    sObserverLists->mCreateObservers.push_back(aObserver);
-  }
-  if (aOp & IOInterposeObserver::OpRead &&
-      !VectorContains(sObserverLists->mReadObservers, aObserver)) {
-    sObserverLists->mReadObservers.push_back(aObserver);
-  }
-  if (aOp & IOInterposeObserver::OpWrite &&
-      !VectorContains(sObserverLists->mWriteObservers, aObserver)) {
-    sObserverLists->mWriteObservers.push_back(aObserver);
-  }
-  if (aOp & IOInterposeObserver::OpFSync &&
-      !VectorContains(sObserverLists->mFSyncObservers, aObserver)) {
-    sObserverLists->mFSyncObservers.push_back(aObserver);
-  }
-  if (aOp & IOInterposeObserver::OpStat &&
-      !VectorContains(sObserverLists->mStatObservers, aObserver)) {
-    sObserverLists->mStatObservers.push_back(aObserver);
-  }
-  if (aOp & IOInterposeObserver::OpClose &&
-      !VectorContains(sObserverLists->mCloseObservers, aObserver)) {
-    sObserverLists->mCloseObservers.push_back(aObserver);
-  }
-
-  // Update field of observed operation with the operations that the new
-  // observer is observing.
-  sObservedOperations = (IOInterposeObserver::Operation)
-                        (sObservedOperations | aOp);
+  sMasterList->Register(aOp, aObserver);
 }
 
-/* static */ void IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
-                                           IOInterposeObserver* aObserver)
+/* static */ void
+IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
+                         IOInterposeObserver* aObserver)
 {
-  if (!sObserverLists) {
+  if (!sMasterList) {
     return;
   }
 
-  AutoPRLock listLock(sObserverLists->mObserverListsLock);
-
-  if (aOp & IOInterposeObserver::OpCreateOrOpen) {
-    VectorRemove(sObserverLists->mCreateObservers, aObserver);
-    if (sObserverLists->mCreateObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations &
-                        ~IOInterposeObserver::OpCreateOrOpen);
-    }
-  }
-  if (aOp & IOInterposeObserver::OpRead) {
-    VectorRemove(sObserverLists->mReadObservers, aObserver);
-    if (sObserverLists->mReadObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations & ~IOInterposeObserver::OpRead);
-    }
-  }
-  if (aOp & IOInterposeObserver::OpWrite) {
-    VectorRemove(sObserverLists->mWriteObservers, aObserver);
-    if (sObserverLists->mWriteObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations & ~IOInterposeObserver::OpWrite);
-    }
-  }
-  if (aOp & IOInterposeObserver::OpFSync) {
-    VectorRemove(sObserverLists->mFSyncObservers, aObserver);
-    if (sObserverLists->mFSyncObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations & ~IOInterposeObserver::OpFSync);
-    }
-  }
-  if (aOp & IOInterposeObserver::OpStat) {
-    VectorRemove(sObserverLists->mStatObservers, aObserver);
-    if (sObserverLists->mStatObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations & ~IOInterposeObserver::OpStat);
-    }
-  }
-  if (aOp & IOInterposeObserver::OpClose) {
-    VectorRemove(sObserverLists->mCloseObservers, aObserver);
-    if (sObserverLists->mCloseObservers.empty()) {
-      sObservedOperations = (IOInterposeObserver::Operation)
-                       (sObservedOperations & ~IOInterposeObserver::OpClose);
-    }
-  }
+  sMasterList->Unregister(aOp, aObserver);
 }
 
 /* static */ void
 IOInterposer::RegisterCurrentThread(bool aIsMainThread)
 {
-  // Right now this is a no-op unless we're running on Metro.
-  // More cross-platform stuff will be added in the near future, stay tuned!
-#if defined(XP_WIN)
-  if (XRE_GetWindowsEnvironment() != WindowsEnvironmentType_Metro ||
-      !sIsMainThread.initialized()) {
+  if (!sThreadLocalData.initialized()) {
     return;
   }
-  sIsMainThread.set(aIsMainThread);
-#endif
+  MOZ_ASSERT(!sThreadLocalData.get());
+  PerThreadData* curThreadData = new PerThreadData(aIsMainThread);
+  sThreadLocalData.set(curThreadData);
 }
 
+/* static */ void
+IOInterposer::UnregisterCurrentThread()
+{
+  if (!sThreadLocalData.initialized()) {
+    return;
+  }
+  PerThreadData* curThreadData = sThreadLocalData.get();
+  MOZ_ASSERT(curThreadData);
+  sThreadLocalData.set(nullptr);
+  delete curThreadData;
+}
+
--- a/xpcom/build/IOInterposer.h
+++ b/xpcom/build/IOInterposer.h
@@ -156,34 +156,31 @@ protected:
  * Remark: Instances of this class will never be created, you should consider it
  * to be a namespace containing static functions. The class is created to
  * facilitate to a private static instance variable sObservedOperations.
  * As we want to access this from an inline static methods, we have to do this
  * trick.
  */
 class IOInterposer MOZ_FINAL
 {
-  // Track whether or not a given type of observation is being observed
-  static IOInterposeObserver::Operation sObservedOperations;
-
   // No instance of class should be created, they'd be empty anyway.
   IOInterposer();
 public:
 
   /**
    * This function must be called from the main-thread when no other threads are
    * running before any of the other methods on this class may be used.
    *
    * IO reports can however, safely assume that IsObservedOperation() will
    * return false, until the IOInterposer is initialized.
    *
    * Remark, it's safe to call this method multiple times, so just call it when
    * you to utilize IO interposing.
    */
-  static void Init();
+  static bool Init();
 
   /**
    * This function must be called from the main thread, and furthermore
    * it must be called when no other threads are executing. Effectively
    * restricting us to calling it only during shutdown.
    *
    * Callers should take care that no other consumers are subscribed to events,
    * as these events will stop when this function is called.
@@ -244,23 +241,36 @@ public:
    * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver)
    *
    * Remark: Init() must be called before observers are unregistered.
    */
   static void Unregister(IOInterposeObserver::Operation aOp,
                          IOInterposeObserver* aObserver);
 
   /**
-   * Registers the current thread with the IOInterposer.
+   * Registers the current thread with the IOInterposer. This must be done to
+   * ensure that per-thread data is created in an orderly fashion.
+   * We could have written this to initialize that data lazily, however this
+   * could have unintended consequences if a thread that is not aware of
+   * IOInterposer was implicitly registered: its per-thread data would never
+   * be deleted because it would not know to unregister itself.
    *
    * @param aIsMainThread true if IOInterposer should treat the current thread
    *                      as the main thread.
    */
   static void
   RegisterCurrentThread(bool aIsMainThread = false);
+
+  /**
+   * Unregisters the current thread with the IOInterposer. This is important
+   * to call when a thread is shutting down because it cleans up data that
+   * is stored in a TLS slot.
+   */
+  static void
+  UnregisterCurrentThread();
 };
 
 class IOInterposerInit
 {
 public:
   IOInterposerInit()
   {
 #if defined(MOZ_ENABLE_PROFILER_SPS)
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -19,16 +19,17 @@
 #include "nsIClassInfoImpl.h"
 #include "nsIProgrammingLanguage.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "pratom.h"
 #include "prlog.h"
 #include "nsIObserverService.h"
 #include "mozilla/HangMonitor.h"
+#include "mozilla/IOInterposer.h"
 #include "mozilla/Services.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/ChaosMode.h"
 
 #ifdef XP_LINUX
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sched.h>
@@ -286,16 +287,18 @@ nsThread::ThreadFunc(void *arg)
 {
   nsThread *self = static_cast<nsThread *>(arg);  // strong reference
   self->mThread = PR_GetCurrentThread();
   SetupCurrentThreadForChaosMode();
 
   // Inform the ThreadManager
   nsThreadManager::get()->RegisterCurrentThread(self);
 
+  mozilla::IOInterposer::RegisterCurrentThread();
+
   // Wait for and process startup event
   nsCOMPtr<nsIRunnable> event;
   if (!self->GetEvent(true, getter_AddRefs(event))) {
     NS_WARNING("failed waiting for thread startup event");
     return;
   }
   event->Run();  // unblocks nsThread::Init
   event = nullptr;
@@ -323,16 +326,18 @@ nsThread::ThreadFunc(void *arg)
           self->mEventsAreDoomed = true;
           break;
         }
       }
       NS_ProcessPendingEvents(self);
     }
   }
 
+  mozilla::IOInterposer::UnregisterCurrentThread();
+
   // Inform the threadmanager that this thread is going away
   nsThreadManager::get()->UnregisterCurrentThread(self);
 
   // Dispatch shutdown ACK
   event = new nsThreadShutdownAckEvent(self->mShutdownContext);
   self->mShutdownContext->joiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
 
   // Release any observer of the thread here.