Bug 449822 - "Service manager can create two instances of a service". r=bsmedberg.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 04 Sep 2008 15:44:41 -0700
changeset 18820 7f5dbe89e781236ffd761ddbb080049581d794a4
parent 18819 8cdeae0c853bb91fc75655312b00bf1c6a81117e
child 18821 27e0838af137ad30cae84f447d2b8eb21bb21afd
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs449822
milestone1.9.1b1pre
Bug 449822 - "Service manager can create two instances of a service". r=bsmedberg.
xpcom/components/nsComponentManager.cpp
xpcom/components/nsComponentManager.h
xpcom/tests/Makefile.in
xpcom/tests/TestRacingServiceManager.cpp
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -77,16 +77,18 @@
 #include "nsISupportsPrimitives.h"
 #include "nsIClassInfo.h"
 #include "nsLocalFile.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
 #include "nsXPIDLString.h"
 #include "prcmon.h"
 #include "xptinfo.h" // this after nsISupports, to pick up IID so that xpt stuff doesn't try to define it itself...
+#include "nsThreadUtils.h"
+#include "prthread.h"
 
 #include "nsInt64.h"
 #include "nsManifestLineReader.h"
 
 #include NEW_H     // for placement new
 
 
 #ifdef XP_BEOS
@@ -96,17 +98,16 @@
 
 #include "prlog.h"
 
 NS_COM PRLogModuleInfo* nsComponentManagerLog = nsnull;
 
 #if 0 || defined (DEBUG_timeless)
  #define SHOW_DENIED_ON_SHUTDOWN
  #define SHOW_CI_ON_EXISTING_SERVICE
- #define XPCOM_CHECK_PENDING_CIDS
 #endif
 
 // Bloated registry buffer size to improve startup performance -- needs to
 // be big enough to fit the entire file into memory or it'll thrash.
 // 512K is big enough to allow for some future growth in the registry.
 #define BIG_REGISTRY_BUFLEN   (512*1024)
 
 // Common Key Names
@@ -1036,56 +1037,16 @@ nsComponentManagerImpl::ReadPersistentRe
             }
             contractIDTableEntry->mContractID = contractID;
             contractIDTableEntry->mContractIDLen = lengths[0];
         }
 
         contractIDTableEntry->mFactoryEntry = cidEntry;
     }
 
-#ifdef XPCOM_CHECK_PENDING_CIDS
-    {
-/*
- * If you get Asserts when you define SHOW_CI_ON_EXISTING_SERVICE and want to
- * track down their cause, then you should add the contracts listed by the
- * assertion to abusedContracts. The next time you run your xpcom app, xpcom
- * will assert the first time the object associated with the contract is
- * instantiated (which in many cases is the source of the problem).
- *
- * If you're doing this then you might want to NOP and soft breakpoint the
- * lines labeled: NOP_AND_BREAK.
- *
- * Otherwise XPCOM will refuse to create the object for the caller, which
- * while reasonable at some level, will almost certainly cause the app to
- * stop functioning normally.
- */
-        static char abusedContracts[][128] = {
-        /*// Example contracts:
-            "@mozilla.org/rdf/container;1",
-            "@mozilla.org/intl/charsetalias;1",
-            "@mozilla.org/locale/win32-locale;1",
-            "@mozilla.org/widget/lookandfeel/win;1",
-        // */
-            0
-        };
-        for (int i=0; abusedContracts[i] && *abusedContracts[i]; i++) {
-            nsFactoryEntry *entry = nsnull;
-            nsContractIDTableEntry* contractIDTableEntry =
-                static_cast<nsContractIDTableEntry*>
-                           (PL_DHashTableOperate(&mContractIDs, abusedContracts[i],
-                                                    PL_DHASH_LOOKUP));
-
-            if (PL_DHASH_ENTRY_IS_BUSY(contractIDTableEntry)) {
-                entry = contractIDTableEntry->mFactoryEntry;
-                AddPendingCID(entry->mCid);
-            }
-        }
-    }
-#endif
-
     if (ReadSectionHeader(reader, "CATEGORIES"))
         goto out;
 
     mCategoryManager->SuppressNotifications(PR_TRUE);
 
     while (1)
     {
         if (!reader.NextLine())
@@ -1576,50 +1537,16 @@ nsComponentManagerImpl::CLSIDToContractI
                 NS_SUCCEEDED(rv) ? *aContractID : "[FAILED]"));
         if (buf)
             PR_Free(buf);
     }
 #endif
     return rv;
 }
 
-#ifdef XPCOM_CHECK_PENDING_CIDS
-
-// This method must be called from within the mMon monitor
-nsresult
-nsComponentManagerImpl::AddPendingCID(const nsCID &aClass)
-{
-    int max = mPendingCIDs.Count();
-    for (int index = 0; index < max; index++)
-    {
-        nsCID *cidp = (nsCID*) mPendingCIDs.ElementAt(index);
-        NS_ASSERTION(cidp, "Bad CID in pending list");
-        if (cidp->Equals(aClass)) {
-            nsXPIDLCString cid;
-            cid.Adopt(aClass.ToString());
-            nsCAutoString message;
-            message = NS_LITERAL_CSTRING("Creation of \"") +
-                      cid + NS_LITERAL_CSTRING("\" in progress (Reentrant GS - see bug 194568)");
-            // Note that you may see this assertion by near-simultaneous
-            // calls to GetService on multiple threads.
-            NS_WARNING(message.get());
-            return NS_ERROR_NOT_AVAILABLE;
-        }
-    }
-    mPendingCIDs.AppendElement((void*)&aClass);
-    return NS_OK;
-}
-
-// This method must be called from within the mMon monitor
-void
-nsComponentManagerImpl::RemovePendingCID(const nsCID &aClass)
-{
-    mPendingCIDs.RemoveElement((void*)&aClass);
-}
-#endif
 /**
  * CreateInstance()
  *
  * Create an instance of an object that implements an interface and belongs
  * to the implementation aClass using the factory. The factory is immediately
  * released and not held onto for any longer.
  */
 NS_IMETHODIMP
@@ -1826,16 +1753,57 @@ nsComponentManagerImpl::FreeServices()
 
     if (mFactories.ops) {
         PL_DHashTableEnumerate(&mFactories, FreeServiceFactoryEntryEnumerate, nsnull);
     }
 
     return NS_OK;
 }
 
+// This should only ever be called within the monitor!
+nsComponentManagerImpl::PendingServiceInfo*
+nsComponentManagerImpl::AddPendingService(const nsCID& aServiceCID,
+                                          PRThread* aThread)
+{
+  PendingServiceInfo* newInfo = mPendingServices.AppendElement();
+  if (newInfo) {
+    newInfo->cid = &aServiceCID;
+    newInfo->thread = aThread;
+  }
+  return newInfo;
+}
+
+// This should only ever be called within the monitor!
+void
+nsComponentManagerImpl::RemovePendingService(const nsCID& aServiceCID)
+{
+  PRUint32 pendingCount = mPendingServices.Length();
+  for (PRUint32 index = 0; index < pendingCount; ++index) {
+    const PendingServiceInfo& info = mPendingServices.ElementAt(index);
+    if (info.cid->Equals(aServiceCID)) {
+      mPendingServices.RemoveElementAt(index);
+      return;
+    }
+  }
+}
+
+// This should only ever be called within the monitor!
+PRThread*
+nsComponentManagerImpl::GetPendingServiceThread(const nsCID& aServiceCID) const
+{
+  PRUint32 pendingCount = mPendingServices.Length();
+  for (PRUint32 index = 0; index < pendingCount; ++index) {
+    const PendingServiceInfo& info = mPendingServices.ElementAt(index);
+    if (info.cid->Equals(aServiceCID)) {
+      return info.thread;
+    }
+  }
+  return nsnull;
+}
+
 NS_IMETHODIMP
 nsComponentManagerImpl::GetService(const nsCID& aClass,
                                    const nsIID& aIID,
                                    void* *result)
 {
     // test this first, since there's no point in returning a service during
     // shutdown -- whether it's available or not would depend on the order it
     // occurs in the list
@@ -1848,17 +1816,16 @@ nsComponentManagerImpl::GetService(const
         fprintf(stderr, "Getting service on shutdown. Denied.\n"
                "         CID: %s\n         IID: %s\n", cid.get(), iid.get());
 #endif /* SHOW_DENIED_ON_SHUTDOWN */
         return NS_ERROR_UNEXPECTED;
     }
 
     nsAutoMonitor mon(mMon);
 
-    nsresult rv = NS_OK;
     nsIDKey key(aClass);
     nsFactoryEntry* entry = nsnull;
     nsFactoryTableEntry* factoryTableEntry =
         static_cast<nsFactoryTableEntry*>
                    (PL_DHashTableOperate(&mFactories, &aClass,
                                             PL_DHASH_LOOKUP));
 
     if (PL_DHASH_ENTRY_IS_BUSY(factoryTableEntry)) {
@@ -1866,50 +1833,106 @@ nsComponentManagerImpl::GetService(const
     }
 
     if (entry && entry->mServiceObject) {
         nsCOMPtr<nsISupports> supports = entry->mServiceObject;
         mon.Exit();
         return supports->QueryInterface(aIID, result);
     }
 
-#ifdef XPCOM_CHECK_PENDING_CIDS
-    rv = AddPendingCID(aClass);
-    if (NS_FAILED(rv))
-        return rv; // NOP_AND_BREAK
+    PRThread* currentPRThread = PR_GetCurrentThread();
+    NS_ASSERTION(currentPRThread, "This should never be null!");
+
+    // Needed to optimize the event loop below.
+    nsIThread* currentThread = nsnull;
+
+    PRThread* pendingPRThread;
+    while ((pendingPRThread = GetPendingServiceThread(aClass))) {
+        if (pendingPRThread == currentPRThread) {
+            NS_ERROR("Recursive GetService!");
+            return NS_ERROR_NOT_AVAILABLE;
+        }
+
+        mon.Exit();
+
+        if (!currentThread) {
+            currentThread = NS_GetCurrentThread();
+            NS_ASSERTION(currentThread, "This should never be null!");
+        }
+
+        // This will process a single event or yield the thread if no event is
+        // pending.
+        if (!NS_ProcessNextEvent(currentThread, PR_FALSE)) {
+            PR_Sleep(PR_INTERVAL_NO_WAIT);
+        }
+
+        mon.Enter();
+    }
+
+    if (currentThread) {
+        // If we have a currentThread then we must have waited on another thread
+        // to create the service. Grab it now if that succeeded.
+        if (!entry) {
+            factoryTableEntry = static_cast<nsFactoryTableEntry*>
+                (PL_DHashTableOperate(&mFactories, &aClass, PL_DHASH_LOOKUP));
+
+            if (PL_DHASH_ENTRY_IS_BUSY(factoryTableEntry)) {
+                entry = factoryTableEntry->mFactoryEntry;
+            }
+        }
+
+        // It's still possible that the other thread failed to create the
+        // service so we're not guaranteed to have an entry or service yet.
+        if (entry && entry->mServiceObject) {
+            nsCOMPtr<nsISupports> supports = entry->mServiceObject;
+            mon.Exit();
+            return supports->QueryInterface(aIID, result);
+        }
+    }
+
+#ifdef DEBUG
+    PendingServiceInfo* newInfo =
 #endif
+    AddPendingService(aClass, currentPRThread);
+    NS_ASSERTION(newInfo, "Failed to add info to the array!");
+
     nsCOMPtr<nsISupports> service;
     // We need to not be holding the service manager's monitor while calling
     // CreateInstance, because it invokes user code which could try to re-enter
     // the service manager:
     mon.Exit();
 
-    rv = CreateInstance(aClass, nsnull, aIID, getter_AddRefs(service));
+    nsresult rv = CreateInstance(aClass, nsnull, aIID, getter_AddRefs(service));
 
     mon.Enter();
 
-#ifdef XPCOM_CHECK_PENDING_CIDS
-    RemovePendingCID(aClass);
+#ifdef DEBUG
+    pendingPRThread = GetPendingServiceThread(aClass);
+    NS_ASSERTION(pendingPRThread == currentPRThread,
+                 "Pending service array has been changed!");
 #endif
+    RemovePendingService(aClass);
 
     if (NS_FAILED(rv))
         return rv;
 
     if (!entry) { // second hash lookup for GetService
         nsFactoryTableEntry* factoryTableEntry =
             static_cast<nsFactoryTableEntry*>
                        (PL_DHashTableOperate(&mFactories, &aClass,
                                                 PL_DHASH_LOOKUP));
         if (PL_DHASH_ENTRY_IS_BUSY(factoryTableEntry)) {
             entry = factoryTableEntry->mFactoryEntry;
         }
         NS_ASSERTION(entry, "we should have a factory entry since CI succeeded - we should not get here");
         if (!entry) return NS_ERROR_FAILURE;
     }
 
+    NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!");
+
     entry->mServiceObject = service;
     *result = service.get();
     if (!*result) {
         NS_ERROR("Factory did not return an object but returned success!");
         return NS_ERROR_SERVICE_NOT_FOUND;
     }
     NS_ADDREF(static_cast<nsISupports*>((*result)));
     return rv;
@@ -2149,75 +2172,104 @@ nsComponentManagerImpl::GetServiceByCont
         fprintf(stderr, "Getting service on shutdown. Denied.\n"
                "  ContractID: %s\n         IID: %s\n", aContractID, iid.get());
 #endif /* SHOW_DENIED_ON_SHUTDOWN */
         return NS_ERROR_UNEXPECTED;
     }
 
     nsAutoMonitor mon(mMon);
 
-    nsresult rv = NS_OK;
     nsFactoryEntry *entry = nsnull;
     nsContractIDTableEntry* contractIDTableEntry =
         static_cast<nsContractIDTableEntry*>
                    (PL_DHashTableOperate(&mContractIDs, aContractID,
                                             PL_DHASH_LOOKUP));
 
-    if (PL_DHASH_ENTRY_IS_BUSY(contractIDTableEntry)) {
-        entry = contractIDTableEntry->mFactoryEntry;
+    if (!PL_DHASH_ENTRY_IS_BUSY(contractIDTableEntry))
+        return NS_ERROR_FACTORY_NOT_REGISTERED;
+
+    entry = contractIDTableEntry->mFactoryEntry;
+    NS_ASSERTION(entry, "This should never be null!");
+
+    if (entry->mServiceObject) {
+        nsCOMPtr<nsISupports> serviceObject = entry->mServiceObject;
+
+        // We need to not be holding the service manager's monitor while calling
+        // QueryInterface, because it invokes user code which could try to re-enter
+        // the service manager, or try to grab some other lock/monitor/condvar
+        // and deadlock, e.g. bug 282743.
+        mon.Exit();
+        return serviceObject->QueryInterface(aIID, result);
     }
 
-    if (entry) {
-        if (entry->mServiceObject) {
-            nsCOMPtr<nsISupports> serviceObject = entry->mServiceObject;
-
-            // We need to not be holding the service manager's monitor while calling
-            // QueryInterface, because it invokes user code which could try to re-enter
-            // the service manager, or try to grab some other lock/monitor/condvar
-            // and deadlock, e.g. bug 282743.
-            mon.Exit();
-            return serviceObject->QueryInterface(aIID, result);
+    PRThread* currentPRThread = PR_GetCurrentThread();
+    NS_ASSERTION(currentPRThread, "This should never be null!");
+
+    // Needed to optimize the event loop below.
+    nsIThread* currentThread = nsnull;
+
+    PRThread* pendingPRThread;
+    while ((pendingPRThread = GetPendingServiceThread(entry->mCid))) {
+        if (pendingPRThread == currentPRThread) {
+            NS_ERROR("Recursive GetService!");
+            return NS_ERROR_NOT_AVAILABLE;
+        }
+
+        mon.Exit();
+
+        if (!currentThread) {
+            currentThread = NS_GetCurrentThread();
+            NS_ASSERTION(currentThread, "This should never be null!");
         }
-#ifdef XPCOM_CHECK_PENDING_CIDS
-        rv = AddPendingCID(entry->mCid);
-        if (NS_FAILED(rv))
-            return rv; // NOP_AND_BREAK
+
+        // This will process a single event or yield the thread if no event is
+        // pending.
+        if (!NS_ProcessNextEvent(currentThread, PR_FALSE)) {
+            PR_Sleep(PR_INTERVAL_NO_WAIT);
+        }
+
+        mon.Enter();
+    }
+
+    if (currentThread && entry->mServiceObject) {
+        // If we have a currentThread then we must have waited on another thread
+        // to create the service. Grab it now if that succeeded.
+        nsCOMPtr<nsISupports> serviceObject = entry->mServiceObject;
+        mon.Exit();
+        return serviceObject->QueryInterface(aIID, result);
+    }
+
+#ifdef DEBUG
+    PendingServiceInfo* newInfo =
 #endif
-    }
+    AddPendingService(entry->mCid, currentPRThread);
+    NS_ASSERTION(newInfo, "Failed to add info to the array!");
 
     nsCOMPtr<nsISupports> service;
     // We need to not be holding the service manager's monitor while calling
     // CreateInstance, because it invokes user code which could try to re-enter
     // the service manager:
     mon.Exit();
 
-    rv = CreateInstanceByContractID(aContractID, nsnull, aIID, getter_AddRefs(service));
+    nsresult rv = CreateInstanceByContractID(aContractID, nsnull, aIID,
+                                             getter_AddRefs(service));
 
     mon.Enter();
 
-#ifdef XPCOM_CHECK_PENDING_CIDS 
-    if (entry)
-        RemovePendingCID(entry->mCid);
+#ifdef DEBUG
+    pendingPRThread = GetPendingServiceThread(entry->mCid);
+    NS_ASSERTION(pendingPRThread == currentPRThread,
+                 "Pending service array has been changed!");
 #endif
+    RemovePendingService(entry->mCid);
 
     if (NS_FAILED(rv))
         return rv;
 
-    if (!entry) { // second hash lookup for GetService
-        nsContractIDTableEntry* contractIDTableEntry =
-            static_cast<nsContractIDTableEntry*>
-                       (PL_DHashTableOperate(&mContractIDs, aContractID,
-                                                PL_DHASH_LOOKUP));
-
-        if (PL_DHASH_ENTRY_IS_BUSY(contractIDTableEntry)) {
-            entry = contractIDTableEntry->mFactoryEntry;
-        }
-        NS_ASSERTION(entry, "we should have a factory entry since CI succeeded - we should not get here");
-        if (!entry) return NS_ERROR_FAILURE;
-    }
+    NS_ASSERTION(!entry->mServiceObject, "Created two instances of a service!");
 
     entry->mServiceObject = service;
     *result = service.get();
     NS_ADDREF(static_cast<nsISupports*>(*result));
     return rv;
 }
 
 NS_IMETHODIMP
--- a/xpcom/components/nsComponentManager.h
+++ b/xpcom/components/nsComponentManager.h
@@ -62,16 +62,17 @@
 #include "nsIFile.h"
 #include "plarena.h"
 #include "nsCOMArray.h"
 #include "nsDataHashtable.h"
 #include "nsTArray.h"
 
 struct nsFactoryEntry;
 class nsIServiceManager;
+struct PRThread;
 
 #define NS_COMPONENTMANAGER_CID                      \
 { /* 91775d60-d5dc-11d2-92fb-00e09805570f */         \
     0x91775d60,                                      \
     0xd5dc,                                          \
     0x11d2,                                          \
     {0x92, 0xfb, 0x00, 0xe0, 0x98, 0x05, 0x57, 0x0f} \
 }
@@ -255,22 +256,27 @@ public:
 
     nsDataHashtable<nsHashableHashKey, PRInt64> mAutoRegEntries;
 
     PRBool              mRegistryDirty;
     nsCOMPtr<nsCategoryManager>  mCategoryManager;
 
     PLArenaPool   mArena;
 
-#ifdef XPCOM_CHECK_PENDING_CIDS
-    nsresult AddPendingCID(const nsCID &aClass);
-    void RemovePendingCID(const nsCID &aClass);
+    struct PendingServiceInfo {
+      const nsCID* cid;
+      PRThread* thread;
+    };
 
-    nsVoidArray         mPendingCIDs;
-#endif
+    inline PendingServiceInfo* AddPendingService(const nsCID& aServiceCID,
+                                                 PRThread* aThread);
+    inline void RemovePendingService(const nsCID& aServiceCID);
+    inline PRThread* GetPendingServiceThread(const nsCID& aServiceCID) const;
+
+    nsTArray<PendingServiceInfo> mPendingServices;
 
 private:
     ~nsComponentManagerImpl();
 };
 
 
 #define NS_MAX_FILENAME_LEN	1024
 
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -72,16 +72,17 @@ CPPSRCS		= \
 		TestID.cpp \
 		TestINIParser.cpp \
 		TestObserverService.cpp \
 		TestServMgr.cpp \
 		TestAutoPtr.cpp \
 		TestVersionComparator.cpp \
 		TestTextFormatter.cpp \
 		TestPipe.cpp \
+		TestRacingServiceManager.cpp \
 		TestRegistrationOrder.cpp \
 		TestProxies.cpp \
 		TestThreadPoolListener.cpp \
 		TestTimers.cpp \
 		TestOOM.cpp \
 		$(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
@@ -136,16 +137,17 @@ CPP_UNIT_TESTS = \
   TestAutoPtr \
   TestCOMPtr \
   TestCOMPtrEq \
   TestFactory \
   TestHashtables \
   TestID \
   TestObserverService \
   TestPipe \
+  TestRacingServiceManager \
   TestServMgr \
   TestTextFormatter \
   TestThreadPoolListener \
   TestTimers \
   $(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPP_UNIT_TESTS += \
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestRacingServiceManager.cpp
@@ -0,0 +1,312 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Racing Service Manager Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Ben Turner <bent.mozilla@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "TestHarness.h"
+
+#include "nsIFactory.h"
+#include "nsIThread.h"
+#include "nsIComponentRegistrar.h"
+
+#include "nsAutoLock.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "prmon.h"
+
+#ifdef DEBUG
+#define TEST_ASSERTION(_test, _msg) \
+    NS_ASSERTION(_test, _msg);
+#else
+#define TEST_ASSERTION(_test, _msg) \
+  PR_BEGIN_MACRO \
+    if (!(_test)) { \
+      NS_DebugBreak(NS_DEBUG_ABORT, _msg, #_test, __FILE__, __LINE__); \
+    } \
+  PR_END_MACRO
+#endif
+
+/* f93f6bdc-88af-42d7-9d64-1b43c649a3e5 */ 
+#define FACTORY_CID1                                 \
+{                                                    \
+  0xf93f6bdc,                                        \
+  0x88af,                                            \
+  0x42d7,                                            \
+  { 0x9d, 0x64, 0x1b, 0x43, 0xc6, 0x49, 0xa3, 0xe5 } \
+}
+NS_DEFINE_CID(kFactoryCID1, FACTORY_CID1);
+
+/* ef38ad65-6595-49f0-8048-e819f81d15e2 */
+#define FACTORY_CID2                                 \
+{                                                    \
+  0xef38ad65,                                        \
+  0x6595,                                            \
+  0x49f0,                                            \
+  { 0x80, 0x48, 0xe8, 0x19, 0xf8, 0x1d, 0x15, 0xe2 } \
+}
+NS_DEFINE_CID(kFactoryCID2, FACTORY_CID2);
+
+#define FACTORY_CONTRACTID                           \
+  "TestRacingThreadManager/factory;1"
+
+PRInt32 gComponent1Count = 0;
+PRInt32 gComponent2Count = 0;
+
+PRMonitor* gMonitor = nsnull;
+
+PRBool gCreateInstanceCalled = PR_FALSE;
+PRBool gMainThreadWaiting = PR_FALSE;
+
+class AutoCreateAndDestroyMonitor
+{
+public:
+  AutoCreateAndDestroyMonitor(PRMonitor** aMonitorPtr)
+  : mMonitorPtr(aMonitorPtr) {
+    *aMonitorPtr =
+      nsAutoMonitor::NewMonitor("TestRacingServiceManager::AutoMon");
+    TEST_ASSERTION(*aMonitorPtr, "Out of memory!");
+  }
+
+  ~AutoCreateAndDestroyMonitor() {
+    if (*mMonitorPtr) {
+      nsAutoMonitor::DestroyMonitor(*mMonitorPtr);
+      *mMonitorPtr = nsnull;
+    }
+  }
+
+private:
+  PRMonitor** mMonitorPtr;
+};
+
+class Factory : public nsIFactory
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  Factory() : mFirstComponentCreated(PR_FALSE) { }
+
+  NS_IMETHOD CreateInstance(nsISupports* aDelegate,
+                            const nsIID& aIID,
+                            void** aResult);
+
+  NS_IMETHOD LockFactory(PRBool aLock) {
+    return NS_OK;
+  }
+
+  PRBool mFirstComponentCreated;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(Factory, nsIFactory)
+
+class Component1 : public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  Component1() {
+    // This is the real test - make sure that only one instance is ever created.
+    PRInt32 count = PR_AtomicIncrement(&gComponent1Count);
+    TEST_ASSERTION(count == 1, "Too many components created!");
+  }
+};
+
+NS_IMPL_THREADSAFE_ADDREF(Component1)
+NS_IMPL_THREADSAFE_RELEASE(Component1)
+
+NS_INTERFACE_MAP_BEGIN(Component1)
+  NS_INTERFACE_MAP_ENTRY(Component1)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+class Component2 : public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  Component2() {
+    // This is the real test - make sure that only one instance is ever created.
+    PRInt32 count = PR_AtomicIncrement(&gComponent2Count);
+    TEST_ASSERTION(count == 1, "Too many components created!");
+  }
+};
+
+NS_IMPL_THREADSAFE_ADDREF(Component2)
+NS_IMPL_THREADSAFE_RELEASE(Component2)
+
+NS_INTERFACE_MAP_BEGIN(Component2)
+  NS_INTERFACE_MAP_ENTRY(Component2)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+Factory::CreateInstance(nsISupports* aDelegate,
+                        const nsIID& aIID,
+                        void** aResult)
+{
+  // Make sure that the second thread beat the main thread to the getService
+  // call.
+  TEST_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  {
+    nsAutoMonitor mon(gMonitor);
+
+    gCreateInstanceCalled = PR_TRUE;
+    mon.Notify();
+
+    mon.Wait(PR_MillisecondsToInterval(3000));
+  }
+
+  NS_ENSURE_FALSE(aDelegate, NS_ERROR_NO_AGGREGATION);
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  nsCOMPtr<nsISupports> instance;
+
+  if (!mFirstComponentCreated) {
+    instance = new Component1();
+  }
+  else {
+    instance = new Component2();
+  }
+  NS_ENSURE_TRUE(instance, NS_ERROR_OUT_OF_MEMORY);
+
+  nsresult rv = instance->QueryInterface(aIID, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+class Runnable : public nsRunnable
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  Runnable() : mFirstRunnableDone(PR_FALSE) { }
+
+  PRBool mFirstRunnableDone;
+};
+
+NS_IMETHODIMP
+Runnable::Run()
+{
+  {
+    nsAutoMonitor mon(gMonitor);
+
+    while (!gMainThreadWaiting) {
+      mon.Wait();
+    }
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsISupports> component;
+
+  if (!mFirstRunnableDone) {
+    component = do_GetService(kFactoryCID1, &rv);
+  }
+  else {
+    component = do_GetService(FACTORY_CONTRACTID, &rv);
+  }
+  TEST_ASSERTION(NS_SUCCEEDED(rv), "GetService failed!");
+
+  return NS_OK;
+}
+
+int main(int argc, char** argv)
+{
+  ScopedXPCOM xpcom("RacingServiceManager");
+  NS_ENSURE_FALSE(xpcom.failed(), 1);
+
+  nsCOMPtr<nsIComponentRegistrar> registrar;
+  nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  nsRefPtr<Factory> factory = new Factory();
+  NS_ENSURE_TRUE(factory, 1);
+
+  rv = registrar->RegisterFactory(kFactoryCID1, nsnull, nsnull, factory);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  rv = registrar->RegisterFactory(kFactoryCID2, nsnull, FACTORY_CONTRACTID,
+                                  factory);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  AutoCreateAndDestroyMonitor mon(&gMonitor);
+
+  nsRefPtr<Runnable> runnable = new Runnable();
+  NS_ENSURE_TRUE(runnable, 1);
+
+  // Run the classID test
+  nsCOMPtr<nsIThread> newThread;
+  rv = NS_NewThread(getter_AddRefs(newThread), runnable);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  {
+    nsAutoMonitor mon(gMonitor);
+
+    gMainThreadWaiting = PR_TRUE;
+    mon.Notify();
+
+    while (!gCreateInstanceCalled) {
+      mon.Wait();
+    }
+  }
+
+  nsCOMPtr<nsISupports> component(do_GetService(kFactoryCID1, &rv));
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  // Reset for the contractID test
+  gMainThreadWaiting = gCreateInstanceCalled = PR_FALSE;
+  factory->mFirstComponentCreated = runnable->mFirstRunnableDone = PR_TRUE;
+  component = nsnull;
+
+  rv = newThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  {
+    nsAutoMonitor mon(gMonitor);
+
+    gMainThreadWaiting = PR_TRUE;
+    mon.Notify();
+
+    while (!gCreateInstanceCalled) {
+      mon.Wait();
+    }
+  }
+
+  component = do_GetService(FACTORY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  return 0;
+}