Back to index

lightning-sunbird  0.9+nobinonly
nsAutoLock.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsAutoLock.h"
00039 
00040 #ifdef DEBUG
00041 
00042 #include "plhash.h"
00043 #include "prprf.h"
00044 #include "prlock.h"
00045 #include "prthread.h"
00046 #include "nsDebug.h"
00047 #include "nsVoidArray.h"
00048 
00049 #ifdef NS_TRACE_MALLOC_XXX
00050 # include <stdio.h>
00051 # include "nsTraceMalloc.h"
00052 #endif
00053 
00054 static PRUintn      LockStackTPI = (PRUintn)-1;
00055 static PLHashTable* OrderTable = 0;
00056 static PRLock*      OrderTableLock = 0;
00057 
00058 static const char* const LockTypeNames[] = {"Lock", "Monitor", "CMonitor"};
00059 
00060 struct nsNamedVector : public nsVoidArray {
00061     const char* mName;
00062 
00063 #ifdef NS_TRACE_MALLOC_XXX
00064     // Callsites for the inner locks/monitors stored in our base nsVoidArray.
00065     // This array parallels our base nsVoidArray.
00066     nsVoidArray mInnerSites;
00067 #endif
00068 
00069     nsNamedVector(const char* name = 0, PRUint32 initialSize = 0)
00070         : nsVoidArray(initialSize),
00071           mName(name)
00072     {
00073     }
00074 };
00075 
00076 static void * PR_CALLBACK
00077 _hash_alloc_table(void *pool, PRSize size)
00078 {
00079     return operator new(size);
00080 }
00081 
00082 static void  PR_CALLBACK
00083 _hash_free_table(void *pool, void *item)
00084 {
00085     operator delete(item);
00086 }
00087 
00088 static PLHashEntry * PR_CALLBACK
00089 _hash_alloc_entry(void *pool, const void *key)
00090 {
00091     return new PLHashEntry;
00092 }
00093 
00094 /*
00095  * Because monitors and locks may be associated with an nsAutoLockBase,
00096  * without having had their associated nsNamedVector created explicitly in
00097  * nsAutoMonitor::NewMonitor/DeleteMonitor, we need to provide a freeEntry
00098  * PLHashTable hook, to avoid leaking nsNamedVectors which are replaced by
00099  * nsAutoMonitor::NewMonitor.
00100  *
00101  * There is still a problem with the OrderTable containing orphaned
00102  * nsNamedVector entries, for manually created locks wrapped by nsAutoLocks.
00103  * (there should be no manually created monitors wrapped by nsAutoMonitors:
00104  * you should use nsAutoMonitor::NewMonitor and nsAutoMonitor::DestroyMonitor
00105  * instead of PR_NewMonitor and PR_DestroyMonitor).  These lock vectors don't
00106  * strictly leak, as they are killed on shutdown, but there are unnecessary
00107  * named vectors in the hash table that outlive their associated locks.
00108  *
00109  * XXX so we should have nsLock, nsMonitor, etc. and strongly type their
00110  * XXX nsAutoXXX counterparts to take only the non-auto types as inputs
00111  */
00112 static void  PR_CALLBACK
00113 _hash_free_entry(void *pool, PLHashEntry *entry, PRUintn flag)
00114 {
00115     nsNamedVector* vec = (nsNamedVector*) entry->value;
00116     if (vec) {
00117         entry->value = 0;
00118         delete vec;
00119     }
00120     if (flag == HT_FREE_ENTRY)
00121         delete entry;
00122 }
00123 
00124 static const PLHashAllocOps _hash_alloc_ops = {
00125     _hash_alloc_table, _hash_free_table,
00126     _hash_alloc_entry, _hash_free_entry
00127 };
00128 
00129 PR_STATIC_CALLBACK(PRIntn)
00130 _purge_one(PLHashEntry* he, PRIntn cnt, void* arg)
00131 {
00132     nsNamedVector* vec = (nsNamedVector*) he->value;
00133 
00134     if (he->key == arg)
00135         return HT_ENUMERATE_REMOVE;
00136     vec->RemoveElement(arg);
00137     return HT_ENUMERATE_NEXT;
00138 }
00139 
00140 PR_STATIC_CALLBACK(void)
00141 OnSemaphoreRecycle(void* addr)
00142 {
00143     if (OrderTable) { 
00144         PR_Lock(OrderTableLock);
00145         PL_HashTableEnumerateEntries(OrderTable, _purge_one, addr);
00146         PR_Unlock(OrderTableLock);
00147     }
00148 }
00149 
00150 PR_STATIC_CALLBACK(PLHashNumber)
00151 _hash_pointer(const void* key)
00152 {
00153     return PLHashNumber(NS_PTR_TO_INT32(key)) >> 2;
00154 }
00155 
00156 // Must be single-threaded here, early in primordial thread.
00157 static void InitAutoLockStatics()
00158 {
00159     (void) PR_NewThreadPrivateIndex(&LockStackTPI, 0);
00160     OrderTable = PL_NewHashTable(64, _hash_pointer,
00161                                  PL_CompareValues, PL_CompareValues,
00162                                  &_hash_alloc_ops, 0);
00163     if (OrderTable && !(OrderTableLock = PR_NewLock())) {
00164         PL_HashTableDestroy(OrderTable);
00165         OrderTable = 0;
00166     }
00167     PR_CSetOnMonitorRecycle(OnSemaphoreRecycle);
00168 }
00169 
00170 void _FreeAutoLockStatics()
00171 {
00172     PLHashTable* table = OrderTable;
00173     if (!table) return;
00174 
00175     // Called at shutdown, so we don't need to lock.
00176     PR_CSetOnMonitorRecycle(0);
00177     PR_DestroyLock(OrderTableLock);
00178     OrderTableLock = 0;
00179     PL_HashTableDestroy(table);
00180     OrderTable = 0;
00181 }
00182 
00183 static nsNamedVector* GetVector(PLHashTable* table, const void* key)
00184 {
00185     PLHashNumber hash = _hash_pointer(key);
00186     PLHashEntry** hep = PL_HashTableRawLookup(table, hash, key);
00187     PLHashEntry* he = *hep;
00188     if (he)
00189         return (nsNamedVector*) he->value;
00190     nsNamedVector* vec = new nsNamedVector();
00191     if (vec)
00192         PL_HashTableRawAdd(table, hep, hash, key, vec);
00193     return vec;
00194 }
00195 
00196 static void OnSemaphoreCreated(const void* key, const char* name )
00197 {
00198     if (key && OrderTable) {
00199         nsNamedVector* value = new nsNamedVector(name);
00200         if (value) {
00201             PR_Lock(OrderTableLock);
00202             PL_HashTableAdd(OrderTable, key, value);
00203             PR_Unlock(OrderTableLock);
00204         }
00205     }
00206 }
00207 
00208 // We maintain an acyclic graph in OrderTable, so recursion can't diverge.
00209 static PRBool Reachable(PLHashTable* table, const void* goal, const void* start)
00210 {
00211     PR_ASSERT(goal);
00212     PR_ASSERT(start);
00213     nsNamedVector* vec = GetVector(table, start);
00214     for (PRUint32 i = 0, n = vec->Count(); i < n; i++) {
00215         void* addr = vec->ElementAt(i);
00216         if (addr == goal || Reachable(table, goal, addr))
00217             return PR_TRUE;
00218     }
00219     return PR_FALSE;
00220 }
00221 
00222 static PRBool WellOrdered(const void* addr1, const void* addr2,
00223                           const void *callsite2, PRUint32* index2p,
00224                           nsNamedVector** vec1p, nsNamedVector** vec2p)
00225 {
00226     PRBool rv = PR_TRUE;
00227     PLHashTable* table = OrderTable;
00228     if (!table) return rv;
00229     PR_Lock(OrderTableLock);
00230 
00231     // Check whether we've already asserted (addr1 < addr2).
00232     nsNamedVector* vec1 = GetVector(table, addr1);
00233     if (vec1) {
00234         PRUint32 i, n;
00235 
00236         for (i = 0, n = vec1->Count(); i < n; i++)
00237             if (vec1->ElementAt(i) == addr2)
00238                 break;
00239 
00240         if (i == n) {
00241             // Now check for (addr2 < addr1) and return false if so.
00242             nsNamedVector* vec2 = GetVector(table, addr2);
00243             if (vec2) {
00244                 for (i = 0, n = vec2->Count(); i < n; i++) {
00245                     void* addri = vec2->ElementAt(i);
00246                     PR_ASSERT(addri);
00247                     if (addri == addr1 || Reachable(table, addr1, addri)) {
00248                         *index2p = i;
00249                         *vec1p = vec1;
00250                         *vec2p = vec2;
00251                         rv = PR_FALSE;
00252                         break;
00253                     }
00254                 }
00255 
00256                 if (rv) {
00257                     // Assert (addr1 < addr2) into the order table.
00258                     // XXX fix plvector/nsVector to use const void*
00259                     vec1->AppendElement((void*) addr2);
00260 #ifdef NS_TRACE_MALLOC_XXX
00261                     vec1->mInnerSites.AppendElement((void*) callsite2);
00262 #endif
00263                 }
00264             }
00265         }
00266     }
00267 
00268     PR_Unlock(OrderTableLock);
00269     return rv;
00270 }
00271 
00272 nsAutoLockBase::nsAutoLockBase(void* addr, nsAutoLockType type)
00273 {
00274     if (LockStackTPI == PRUintn(-1))
00275         InitAutoLockStatics();
00276 
00277     nsAutoLockBase* stackTop =
00278         (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00279     if (stackTop) {
00280         if (stackTop->mAddr == addr) {
00281             // Ignore reentry: it's legal for monitors, and NSPR will assert
00282             // if you reenter a PRLock.
00283         } else if (!addr) {
00284             // Ignore null addresses: the caller promises not to use the
00285             // lock at all, and NSPR will assert if you enter it.
00286         } else {
00287             const void* node =
00288 #ifdef NS_TRACE_MALLOC_XXX
00289                 NS_GetStackTrace(1)
00290 #else
00291                 nsnull
00292 #endif
00293                 ;
00294             nsNamedVector* vec1;
00295             nsNamedVector* vec2;
00296             PRUint32 i2;
00297 
00298             if (!WellOrdered(stackTop->mAddr, addr, node, &i2, &vec1, &vec2)) {
00299                 char buf[128];
00300                 PR_snprintf(buf, sizeof buf,
00301                             "Potential deadlock between %s%s@%p and %s%s@%p",
00302                             vec1->mName ? vec1->mName : "",
00303                             LockTypeNames[stackTop->mType],
00304                             stackTop->mAddr,
00305                             vec2->mName ? vec2->mName : "",
00306                             LockTypeNames[type],
00307                             addr);
00308 #ifdef NS_TRACE_MALLOC_XXX
00309                 fprintf(stderr, "\n*** %s\n\nCurrent stack:\n", buf);
00310                 NS_DumpStackTrace(node, stderr);
00311 
00312                 fputs("\nPrevious stack:\n", stderr);
00313                 NS_DumpStackTrace(vec2->mInnerSites.ElementAt(i2), stderr);
00314                 putc('\n', stderr);
00315 #endif
00316                 NS_ERROR(buf);
00317             }
00318         }
00319     }
00320 
00321     mAddr = addr;
00322     mDown = stackTop;
00323     mType = type;
00324     if (mAddr)
00325         (void) PR_SetThreadPrivate(LockStackTPI, this);
00326 }
00327 
00328 nsAutoLockBase::~nsAutoLockBase()
00329 {
00330     if (mAddr)
00331         (void) PR_SetThreadPrivate(LockStackTPI, mDown);
00332 }
00333 
00334 void nsAutoLockBase::Show()
00335 {
00336     if (!mAddr)
00337         return;
00338     nsAutoLockBase* curr = (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00339     nsAutoLockBase* prev = nsnull;
00340     while (curr != mDown) {
00341         prev = curr;
00342         curr = prev->mDown;
00343     }
00344     if (!prev)
00345         PR_SetThreadPrivate(LockStackTPI, this);
00346     else
00347         prev->mDown = this;
00348 }
00349 
00350 void nsAutoLockBase::Hide()
00351 {
00352     if (!mAddr)
00353         return;
00354     nsAutoLockBase* curr = (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00355     nsAutoLockBase* prev = nsnull;
00356     while (curr != this) {
00357         prev = curr;
00358         curr = prev->mDown;
00359     }
00360     if (!prev)
00361         PR_SetThreadPrivate(LockStackTPI, mDown);
00362     else
00363         prev->mDown = mDown;
00364 }
00365 
00366 nsAutoUnlockBase::nsAutoUnlockBase(void* addr)
00367 {
00368     if (addr)
00369     {
00370         nsAutoLockBase* curr = (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00371         while (curr && curr->mAddr != addr)
00372             curr = curr->mDown;
00373 
00374         mLock = curr;
00375     }
00376     else
00377         mLock = nsnull;
00378 
00379     if (mLock)
00380         mLock->Hide();
00381 }
00382 
00383 nsAutoUnlockBase::~nsAutoUnlockBase()
00384 {
00385     if (mLock)
00386         mLock->Show();
00387 }
00388 
00389 #endif /* DEBUG */
00390 
00391 PRLock* nsAutoLock::NewLock(const char* name)
00392 {
00393     PRLock* lock = PR_NewLock();
00394 #ifdef DEBUG
00395     OnSemaphoreCreated(lock, name);
00396 #endif
00397     return lock;
00398 }
00399 
00400 void nsAutoLock::DestroyLock(PRLock* lock)
00401 {
00402 #ifdef DEBUG
00403     OnSemaphoreRecycle(lock);
00404 #endif
00405     PR_DestroyLock(lock);
00406 }
00407 
00408 PRMonitor* nsAutoMonitor::NewMonitor(const char* name)
00409 {
00410     PRMonitor* mon = PR_NewMonitor();
00411 #ifdef DEBUG
00412     OnSemaphoreCreated(mon, name);
00413 #endif
00414     return mon;
00415 }
00416 
00417 void nsAutoMonitor::DestroyMonitor(PRMonitor* mon)
00418 {
00419 #ifdef DEBUG
00420     OnSemaphoreRecycle(mon);
00421 #endif
00422     PR_DestroyMonitor(mon);
00423 }
00424 
00425 void nsAutoMonitor::Enter()
00426 {
00427 #ifdef DEBUG
00428     if (!mAddr) {
00429         NS_ERROR("It is not legal to enter a null monitor");
00430         return;
00431     }
00432     nsAutoLockBase* stackTop =
00433         (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00434     NS_ASSERTION(stackTop == mDown, "non-LIFO nsAutoMonitor::Enter");
00435     mDown = stackTop;
00436     (void) PR_SetThreadPrivate(LockStackTPI, this);
00437 #endif
00438     PR_EnterMonitor(mMonitor);
00439     mLockCount += 1;
00440 }
00441 
00442 void nsAutoMonitor::Exit()
00443 {
00444 #ifdef DEBUG
00445     if (!mAddr) {
00446         NS_ERROR("It is not legal to exit a null monitor");
00447         return;
00448     }
00449     (void) PR_SetThreadPrivate(LockStackTPI, mDown);
00450 #endif
00451     PRStatus status = PR_ExitMonitor(mMonitor);
00452     NS_ASSERTION(status == PR_SUCCESS, "PR_ExitMonitor failed");
00453     mLockCount -= 1;
00454 }
00455 
00456 // XXX we don't worry about cached monitors being destroyed behind our back.
00457 // XXX current NSPR (mozilla/nsprpub/pr/src/threads/prcmon.c) never destroys
00458 // XXX a cached monitor! potential resource pig in conjunction with necko...
00459 
00460 void nsAutoCMonitor::Enter()
00461 {
00462 #ifdef DEBUG
00463     nsAutoLockBase* stackTop =
00464         (nsAutoLockBase*) PR_GetThreadPrivate(LockStackTPI);
00465     NS_ASSERTION(stackTop == mDown, "non-LIFO nsAutoCMonitor::Enter");
00466     mDown = stackTop;
00467     (void) PR_SetThreadPrivate(LockStackTPI, this);
00468 #endif
00469     PR_CEnterMonitor(mLockObject);
00470     mLockCount += 1;
00471 }
00472 
00473 void nsAutoCMonitor::Exit()
00474 {
00475 #ifdef DEBUG
00476     (void) PR_SetThreadPrivate(LockStackTPI, mDown);
00477 #endif
00478     PRStatus status = PR_CExitMonitor(mLockObject);
00479     NS_ASSERTION(status == PR_SUCCESS, "PR_CExitMonitor failed");
00480     mLockCount -= 1;
00481 }