Bug 774388 - Patch 5: Properly manage the lifetime of the compositor thread, by waiting for CrossProcessCompositorParents to die before destroying it - r=nical,mattwoodrow
☠☠ backed out by 2750c7e119be ☠ ☠
authorBenoit Jacob <bjacob@mozilla.com>
Fri, 06 Jun 2014 09:51:26 -0400
changeset 188948 66f9a0038c679179b44f472375f86c827ec52b5b
parent 188947 275ed6b009f6f8369f029f1310cd77c35ee53c00
child 188949 570cc828108500481427c6a381e5b4bc36d5b7d1
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersnical, mattwoodrow
bugs774388
milestone33.0a1
Bug 774388 - Patch 5: Properly manage the lifetime of the compositor thread, by waiting for CrossProcessCompositorParents to die before destroying it - r=nical,mattwoodrow
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
gfx/thebes/gfxPlatform.cpp
xpcom/build/nsXPComInit.cpp
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -48,16 +48,18 @@
 #include "mozilla/layers/CompositorD3D11.h"
 #include "mozilla/layers/CompositorD3D9.h"
 #endif
 #include "GeckoProfiler.h"
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/unused.h"
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Monitor.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace base;
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace std;
@@ -68,155 +70,258 @@ CompositorParent::LayerTreeState::LayerT
   , mCrossProcessParent(nullptr)
   , mLayerTree(nullptr)
 {
 }
 
 typedef map<uint64_t, CompositorParent::LayerTreeState> LayerTreeMap;
 static LayerTreeMap sIndirectLayerTrees;
 
-// FIXME/bug 774386: we're assuming that there's only one
-// CompositorParent, but that's not always true.  This assumption only
-// affects CrossProcessCompositorParent below.
-static Thread* sCompositorThread = nullptr;
-// manual reference count of the compositor thread.
-static int sCompositorThreadRefCount = 0;
-static MessageLoop* sMainLoop = nullptr;
+/**
+  * A global map referencing each compositor by ID.
+  *
+  * This map is used by the ImageBridge protocol to trigger
+  * compositions without having to keep references to the
+  * compositor
+  */
+typedef map<uint64_t,CompositorParent*> CompositorMap;
+static CompositorMap* sCompositorMap;
+
+static void CreateCompositorMap()
+{
+  MOZ_ASSERT(!sCompositorMap);
+  sCompositorMap = new CompositorMap;
+}
+
+static void DestroyCompositorMap()
+{
+  MOZ_ASSERT(sCompositorMap);
+  MOZ_ASSERT(sCompositorMap->empty());
+  delete sCompositorMap;
+  sCompositorMap = nullptr;
+}
 
 // See ImageBridgeChild.cpp
 void ReleaseImageBridgeParentSingleton();
 
-static void DeferredDeleteCompositorParent(CompositorParent* aNowReadyToDie)
-{
-  aNowReadyToDie->Release();
-}
-
-static void DeleteCompositorThread()
+/*
+ * CompositorThreadHolder is a singleton class that represents the lifetime
+ * of the compositor thread. Its constructor creates the compositor thread,
+ * and its destructor waits for CrossProcessCompositorParent's (CPCPs) to
+ * be destroyed and then destroys the compositor thread.
+ *
+ * The CompositorThreadHolder singleton must be created/destroyed on the main thread.
+ * CompositorParent's must hold a strong reference to it.
+ * CrossProcessCompositorParent's (CPCPs) must call
+ * AddCPCPReference/RemoveCPCPReference to ensure that the compositor thread
+ * destruction will wait for CPCPs to be destroyed first.
+ */
+class CompositorThreadHolder MOZ_FINAL
 {
-  if (NS_IsMainThread()){
-    ReleaseImageBridgeParentSingleton();
-    delete sCompositorThread;
-    sCompositorThread = nullptr;
-  } else {
-    sMainLoop->PostTask(FROM_HERE, NewRunnableFunction(&DeleteCompositorThread));
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorThreadHolder)
+
+public:
+  CompositorThreadHolder()
+    : mCompositorThread(CreateCompositorThread())
+    , mCPCPReferencesMonitor("CompositorThreadHolder CPCP references monitor")
+    , mCPCPRefCnt(0)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_COUNT_CTOR(CompositorThreadHolder);
   }
-}
+
+  ~CompositorThreadHolder()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mCPCPRefCnt == 0, "You should have called WaitForCPCPs before!");
 
-static void ReleaseCompositorThread()
-{
-  if(--sCompositorThreadRefCount == 0) {
-    DeleteCompositorThread();
+    MOZ_COUNT_DTOR(CompositorThreadHolder);
+
+    DestroyCompositorThread(mCompositorThread);
   }
-}
+
+  Thread* GetCompositorThread() const {
+    return mCompositorThread;
+  }
 
-static void SetThreadPriority()
-{
-  hal::SetCurrentThreadPriority(hal::THREAD_PRIORITY_COMPOSITOR);
-}
+  void WaitForCPCPs()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MonitorAutoLock autoLock(mCPCPReferencesMonitor);
+    while(mCPCPRefCnt) {
+      mCPCPReferencesMonitor.Wait();
+    }
+  }
 
-void CompositorParent::StartUp()
-{
-  CreateCompositorMap();
-  CreateThread();
-  sMainLoop = MessageLoop::current();
-}
+  void AddCPCPReference()
+  {
+    MonitorAutoLock autoLock(mCPCPReferencesMonitor);
+    mCPCPRefCnt++;
+  }
+
+  void ReleaseCPCPReference()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MonitorAutoLock autoLock(mCPCPReferencesMonitor);
+    MOZ_ASSERT(mCPCPRefCnt > 0, "dup release");
+    mCPCPRefCnt--;
+    if (mCPCPRefCnt == 0) {
+      mCPCPReferencesMonitor.NotifyAll();
+    }
+  }
+
+private:
 
-void CompositorParent::ShutDown()
-{
-  DestroyThread();
-  DestroyCompositorMap();
-}
+  Thread* const mCompositorThread;
+
+  /* Everywhere in this class, CPCP is short for CrossProcessCompositorParent.
+   * mCPCPRefCnt is the number of CPCPs referencing the compositor thread.
+   * It is not atomic because it is protected behind a monitor, mCPCPReferencesMonitor.
+   */
+  Monitor mCPCPReferencesMonitor;
+  int mCPCPRefCnt;
+
+  static Thread* CreateCompositorThread();
+  static void DestroyCompositorThread(Thread* aCompositorThread);
 
-bool CompositorParent::CreateThread()
+  friend class CompositorParent;
+};
+
+static StaticRefPtr<CompositorThreadHolder> sCompositorThreadHolder;
+
+static MessageLoop* sMainLoop = nullptr;
+
+/* static */ Thread*
+CompositorThreadHolder::CreateCompositorThread()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
-  MOZ_ASSERT(!sCompositorThread);
-  sCompositorThreadRefCount = 1;
-  sCompositorThread = new Thread("Compositor");
+  MOZ_ASSERT(NS_IsMainThread());
+  sMainLoop = MessageLoop::current();
+
+  MOZ_ASSERT(!sCompositorThreadHolder, "The compositor thread has already been started!");
+
+  Thread* compositorThread = new Thread("Compositor");
 
   Thread::Options options;
   /* Timeout values are powers-of-two to enable us get better data.
      128ms is chosen for transient hangs because 8Hz should be the minimally
      acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
   options.transient_hang_timeout = 128; // milliseconds
   /* 8192ms is chosen for permanent hangs because it's several seconds longer
      than the default hang timeout on major platforms (about 5 seconds). */
   options.permanent_hang_timeout = 8192; // milliseconds
 
-  if (!sCompositorThread->StartWithOptions(options)) {
-    delete sCompositorThread;
-    sCompositorThread = nullptr;
-    return false;
+  if (!compositorThread->StartWithOptions(options)) {
+    delete compositorThread;
+    return nullptr;
   }
 
-  return true;
+  CreateCompositorMap();
+
+  return compositorThread;
+}
+
+/* static */ void
+CompositorThreadHolder::DestroyCompositorThread(Thread* aCompositorThread)
+{
+  MOZ_ASSERT(!sCompositorThreadHolder, "We shouldn't be destroying the compositor thread yet.");
+
+  if (NS_IsMainThread()) {
+    DestroyCompositorMap();
+    ReleaseImageBridgeParentSingleton();
+    delete aCompositorThread;
+  } else {
+    sMainLoop->PostTask(FROM_HERE, NewRunnableFunction(&CompositorThreadHolder::DestroyCompositorThread, aCompositorThread));
+  }
+}
+
+static Thread* CompositorThread() {
+  return sCompositorThreadHolder ? sCompositorThreadHolder->GetCompositorThread() : nullptr;
 }
 
-void CompositorParent::DestroyThread()
+static void DeferredDeleteCompositorParent(CompositorParent* aNowReadyToDie)
+{
+  aNowReadyToDie->Release();
+}
+
+static void SetThreadPriority()
+{
+  hal::SetCurrentThreadPriority(hal::THREAD_PRIORITY_COMPOSITOR);
+}
+
+void CompositorParent::StartUpCompositorThread()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
-  ReleaseCompositorThread();
+  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!");
+  MOZ_ASSERT(!sCompositorThreadHolder, "The compositor thread has already been started!");
+
+  sCompositorThreadHolder = new CompositorThreadHolder();
+}
+
+void CompositorParent::ShutDownCompositorThreadWhenCompositorParentsGone()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!");
+  MOZ_ASSERT(sCompositorThreadHolder, "The compositor thread has already been shut down!");
+
+  sCompositorThreadHolder->WaitForCPCPs();
+  sCompositorThreadHolder = nullptr;
 }
 
 MessageLoop* CompositorParent::CompositorLoop()
 {
-  return sCompositorThread ? sCompositorThread->message_loop() : nullptr;
+  return CompositorThread() ? CompositorThread()->message_loop() : nullptr;
 }
 
 CompositorParent::CompositorParent(nsIWidget* aWidget,
                                    bool aUseExternalSurfaceSize,
                                    int aSurfaceWidth, int aSurfaceHeight)
   : mWidget(aWidget)
   , mCurrentCompositeTask(nullptr)
   , mIsTesting(false)
   , mPendingTransaction(0)
   , mPaused(false)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mEGLSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
+  , mCompositorThreadHolder(sCompositorThreadHolder)
 {
-  MOZ_ASSERT(sCompositorThread != nullptr,
-             "The compositor thread must be Initialized before instanciating a CmpositorParent.");
+  MOZ_ASSERT(CompositorThread(),
+             "The compositor thread must be Initialized before instanciating a CompositorParent.");
   MOZ_COUNT_CTOR(CompositorParent);
   mCompositorID = 0;
   // FIXME: This holds on the the fact that right now the only thing that
   // can destroy this instance is initialized on the compositor thread after
   // this task has been processed.
   CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(&AddCompositor,
                                                           this, &mCompositorID));
 
   CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(SetThreadPriority));
 
   mRootLayerTreeID = AllocateLayerTreeId();
   sIndirectLayerTrees[mRootLayerTreeID].mParent = this;
 
   mApzcTreeManager = new APZCTreeManager();
-  ++sCompositorThreadRefCount;
 }
 
 bool
 CompositorParent::IsInCompositorThread()
 {
-  return sCompositorThread && sCompositorThread->thread_id() == PlatformThread::CurrentId();
+  return CompositorThread() && CompositorThread()->thread_id() == PlatformThread::CurrentId();
 }
 
 uint64_t
 CompositorParent::RootLayerTreeId()
 {
   return mRootLayerTreeID;
 }
 
 CompositorParent::~CompositorParent()
 {
   MOZ_COUNT_DTOR(CompositorParent);
-
-  ReleaseCompositorThread();
 }
 
 void
 CompositorParent::Destroy()
 {
   NS_ABORT_IF_FALSE(ManagedPLayerTransactionParent().Length() == 0,
                     "CompositorParent destroyed before managed PLayerTransactionParent");
 
@@ -914,37 +1019,16 @@ CompositorParent::AllocPLayerTransaction
 
 bool
 CompositorParent::DeallocPLayerTransactionParent(PLayerTransactionParent* actor)
 {
   static_cast<LayerTransactionParent*>(actor)->ReleaseIPDLReference();
   return true;
 }
 
-
-typedef map<uint64_t,CompositorParent*> CompositorMap;
-static CompositorMap* sCompositorMap;
-
-void CompositorParent::CreateCompositorMap()
-{
-  if (sCompositorMap == nullptr) {
-    sCompositorMap = new CompositorMap;
-  }
-}
-
-void CompositorParent::DestroyCompositorMap()
-{
-  if (sCompositorMap != nullptr) {
-    NS_ASSERTION(sCompositorMap->empty(),
-                 "The Compositor map should be empty when destroyed>");
-    delete sCompositorMap;
-    sCompositorMap = nullptr;
-  }
-}
-
 CompositorParent* CompositorParent::GetCompositor(uint64_t id)
 {
   CompositorMap::iterator it = sCompositorMap->find(id);
   return it != sCompositorMap->end() ? it->second : nullptr;
 }
 
 void CompositorParent::AddCompositor(CompositorParent* compositor, uint64_t* outID)
 {
@@ -1137,16 +1221,34 @@ private:
 
   // There can be many CPCPs, and IPDL-generated code doesn't hold a
   // reference to top-level actors.  So we hold a reference to
   // ourself.  This is released (deferred) in ActorDestroy().
   nsRefPtr<CrossProcessCompositorParent> mSelfRef;
   Transport* mTransport;
   // Child side's process Id.
   base::ProcessId mChildProcessId;
+
+  struct ScopedCompositorThreadReference
+  {
+    ScopedCompositorThreadReference()
+    {
+      MOZ_ASSERT(sCompositorThreadHolder);
+      sCompositorThreadHolder->AddCPCPReference();
+    }
+
+    ~ScopedCompositorThreadReference()
+    {
+      MOZ_ASSERT(!NS_IsMainThread());
+      MOZ_ASSERT(sCompositorThreadHolder);
+      sCompositorThreadHolder->ReleaseCPCPReference();
+    }
+  };
+
+  ScopedCompositorThreadReference mCompositorThreadReference;
 };
 
 void
 CompositorParent::DidComposite()
 {
   if (mPendingTransaction) {
     unused << SendDidComposite(0, mPendingTransaction);
     mPendingTransaction = 0;
@@ -1168,16 +1270,18 @@ OpenCompositor(CrossProcessCompositorPar
 {
   DebugOnly<bool> ok = aCompositor->Open(aTransport, aHandle, aIOLoop);
   MOZ_ASSERT(ok);
 }
 
 /*static*/ PCompositorParent*
 CompositorParent::Create(Transport* aTransport, ProcessId aOtherProcess)
 {
+  gfxPlatform::InitLayersIPC();
+
   nsRefPtr<CrossProcessCompositorParent> cpcp =
     new CrossProcessCompositorParent(aTransport, aOtherProcess);
   ProcessHandle handle;
   if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
     // XXX need to kill |aOtherProcess|, it's boned
     return nullptr;
   }
 
@@ -1399,25 +1503,24 @@ CrossProcessCompositorParent::GetComposi
 }
 
 void
 CrossProcessCompositorParent::DeferredDestroy()
 {
   CrossProcessCompositorParent* self;
   mSelfRef.forget(&self);
 
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewNonOwningRunnableMethod(self, &CrossProcessCompositorParent::Release);
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+  MOZ_ASSERT(XRE_GetIOMessageLoop());
+  XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
+                                   NewRunnableMethod(self, &CrossProcessCompositorParent::Release));
 }
 
 CrossProcessCompositorParent::~CrossProcessCompositorParent()
 {
-  XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
-                                   new DeleteTask<Transport>(mTransport));
+  delete mTransport;
 }
 
 IToplevelProtocol*
 CrossProcessCompositorParent::CloneToplevel(const InfallibleTArray<mozilla::ipc::ProtocolFdMapping>& aFds,
                                             base::ProcessHandle aPeerProcess,
                                             mozilla::ipc::ProtocolCloneContext* aCtx)
 {
   for (unsigned int i = 0; i < aFds.Length(); i++) {
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -58,16 +58,18 @@ struct ScopedLayerTreeRegistration
                               Layer* aRoot,
                               GeckoContentController* aController);
   ~ScopedLayerTreeRegistration();
 
 private:
   uint64_t mLayersId;
 };
 
+class CompositorThreadHolder;
+
 class CompositorParent : public PCompositorParent,
                          public ShadowLayersManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorParent)
 
 public:
   CompositorParent(nsIWidget* aWidget,
                    bool aUseExternalSurfaceSize = false,
@@ -158,22 +160,25 @@ public:
    *
    * This message loop is used by CompositorParent and ImageBridgeParent.
    */
   static MessageLoop* CompositorLoop();
 
   /**
    * Creates the compositor thread and the global compositor map.
    */
-  static void StartUp();
+  static void StartUpCompositorThread();
 
   /**
-   * Destroys the compositor thread and the global compositor map.
+   * Drops the static reference to the compositor thread holder,
+   * and wait for CrossProcessCompositorParent's to be gone,
+   * allowing the compositor thread shutdown to occur
+   * as soon as CompositorParent's will be gone.
    */
-  static void ShutDown();
+  static void ShutDownCompositorThreadWhenCompositorParentsGone();
 
   /**
    * Allocate an ID that can be used to refer to a layer tree and
    * associated resources that live only on the compositor thread.
    *
    * Must run on the content main thread.
    */
   static uint64_t AllocateLayerTreeId();
@@ -255,46 +260,16 @@ protected:
   void InitializeLayerManager(const nsTArray<LayersBackend>& aBackendHints);
   void PauseComposition();
   void ResumeComposition();
   void ResumeCompositionAndResize(int width, int height);
   void ForceComposition();
   void CancelCurrentCompositeTask();
 
   /**
-   * Creates a global map referencing each compositor by ID.
-   *
-   * This map is used by the ImageBridge protocol to trigger
-   * compositions without having to keep references to the
-   * compositor
-   */
-  static void CreateCompositorMap();
-  static void DestroyCompositorMap();
-
-  /**
-   * Creates the compositor thread.
-   *
-   * All compositors live on the same thread.
-   * The thread is not lazily created on first access to avoid dealing with
-   * thread safety. Therefore it's best to create and destroy the thread when
-   * we know we areb't using it (So creating/destroying along with gfxPlatform
-   * looks like a good place).
-   */
-  static bool CreateThread();
-
-  /**
-   * Destroys the compositor thread.
-   *
-   * It is safe to call this fucntion more than once, although the second call
-   * will have no effect.
-   * This function is not thread-safe.
-   */
-  static void DestroyThread();
-
-  /**
    * Add a compositor to the global compositor map.
    */
   static void AddCompositor(CompositorParent* compositor, uint64_t* id);
   /**
    * Remove a compositor from the global compositor map.
    */
   static CompositorParent* RemoveCompositor(uint64_t id);
 
@@ -331,15 +306,17 @@ protected:
   uint64_t mCompositorID;
   uint64_t mRootLayerTreeID;
 
   bool mOverrideComposeReadiness;
   CancelableTask* mForceCompositionTask;
 
   nsRefPtr<APZCTreeManager> mApzcTreeManager;
 
+  const nsRefPtr<CompositorThreadHolder> mCompositorThreadHolder;
+
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
 
 } // layers
 } // mozilla
 
 #endif // mozilla_layers_CompositorParent_h
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -507,17 +507,17 @@ gfxPlatform::InitLayersIPC()
     }
     sLayersIPCIsUp = true;
 
     AsyncTransactionTrackersHolder::Initialize();
 
     if (UsesOffMainThreadCompositing() &&
         XRE_GetProcessType() == GeckoProcessType_Default)
     {
-        mozilla::layers::CompositorParent::StartUp();
+        mozilla::layers::CompositorParent::StartUpCompositorThread();
         if (gfxPrefs::AsyncVideoEnabled()) {
             mozilla::layers::ImageBridgeChild::StartUp();
         }
 #ifdef MOZ_WIDGET_GONK
         SharedBufferManagerChild::StartUp();
 #endif
     }
 }
@@ -535,17 +535,17 @@ gfxPlatform::ShutdownLayersIPC()
     {
         // This must happen after the shutdown of media and widgets, which
         // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification.
         layers::ImageBridgeChild::ShutDown();
 #ifdef MOZ_WIDGET_GONK
         layers::SharedBufferManagerChild::ShutDown();
 #endif
 
-        layers::CompositorParent::ShutDown();
+        layers::CompositorParent::ShutDownCompositorThreadWhenCompositorParentsGone();
     }
 }
 
 gfxPlatform::~gfxPlatform()
 {
     mScreenReferenceSurface = nullptr;
     mScreenReferenceDrawTarget = nullptr;
 
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -794,27 +794,27 @@ ShutdownXPCOM(nsIServiceManager* servMgr
             if (NS_SUCCEEDED(rv))
             {
                 (void) observerService->
                     NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                     nullptr);
             }
         }
 
+        // This must happen after the shutdown of media and widgets, which
+        // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification.
         NS_ProcessPendingEvents(thread);
+        gfxPlatform::ShutdownLayersIPC();
+
         mozilla::scache::StartupCache::DeleteSingleton();
         if (observerService)
             (void) observerService->
                 NotifyObservers(nullptr, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
                                 nullptr);
 
-        // This must happen after the shutdown of media and widgets, which
-        // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification.
-        gfxPlatform::ShutdownLayersIPC();
-
         gXPCOMThreadsShutDown = true;
         NS_ProcessPendingEvents(thread);
 
         // Shutdown the timer thread and all timers that might still be alive before
         // shutting down the component manager
         nsTimerImpl::Shutdown();
 
         NS_ProcessPendingEvents(thread);