Back to index

lightning-sunbird  0.9+nobinonly
prcmon.c
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 the Netscape Portable Runtime (NSPR).
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-2000
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 the GNU General Public License Version 2 or later (the "GPL"), or
00026  * 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 "primpl.h"
00039 
00040 #include <stdlib.h>
00041 #include <stddef.h>
00042 
00043 /* Lock used to lock the monitor cache */
00044 #ifdef _PR_NO_PREEMPT
00045 #define _PR_NEW_LOCK_MCACHE()
00046 #define _PR_LOCK_MCACHE()
00047 #define _PR_UNLOCK_MCACHE()
00048 #else
00049 #ifdef _PR_LOCAL_THREADS_ONLY
00050 #define _PR_NEW_LOCK_MCACHE()
00051 #define _PR_LOCK_MCACHE() { PRIntn _is; _PR_INTSOFF(_is)
00052 #define _PR_UNLOCK_MCACHE() _PR_INTSON(_is); }
00053 #else
00054 PRLock *_pr_mcacheLock;
00055 #define _PR_NEW_LOCK_MCACHE() (_pr_mcacheLock = PR_NewLock())
00056 #define _PR_LOCK_MCACHE() PR_Lock(_pr_mcacheLock)
00057 #define _PR_UNLOCK_MCACHE() PR_Unlock(_pr_mcacheLock)
00058 #endif
00059 #endif
00060 
00061 /************************************************************************/
00062 
00063 typedef struct MonitorCacheEntryStr MonitorCacheEntry;
00064 
00065 struct MonitorCacheEntryStr {
00066     MonitorCacheEntry*  next;
00067     void*               address;
00068     PRMonitor*          mon;
00069     long                cacheEntryCount;
00070 };
00071 
00072 static PRUint32 hash_mask;
00073 static PRUintn num_hash_buckets;
00074 static PRUintn num_hash_buckets_log2;
00075 static MonitorCacheEntry **hash_buckets;
00076 static MonitorCacheEntry *free_entries;
00077 static PRUintn num_free_entries;
00078 static PRBool expanding;
00079 int _pr_mcache_ready;
00080 
00081 static void (*OnMonitorRecycle)(void *address);
00082 
00083 #define HASH(address)                               \
00084     ((PRUint32) ( ((PRUptrdiff)(address) >> 2) ^    \
00085                   ((PRUptrdiff)(address) >> 10) )   \
00086      & hash_mask)
00087 
00088 /*
00089 ** Expand the monitor cache. This grows the hash buckets and allocates a
00090 ** new chunk of cache entries and throws them on the free list. We keep
00091 ** as many hash buckets as there are entries.
00092 **
00093 ** Because we call malloc and malloc may need the monitor cache, we must
00094 ** ensure that there are several free monitor cache entries available for
00095 ** malloc to get. FREE_THRESHOLD is used to prevent monitor cache
00096 ** starvation during monitor cache expansion.
00097 */
00098 
00099 #define FREE_THRESHOLD  5
00100 
00101 static PRStatus ExpandMonitorCache(PRUintn new_size_log2)
00102 {
00103     MonitorCacheEntry **old_hash_buckets, *p;
00104     PRUintn i, entries, old_num_hash_buckets, added;
00105     MonitorCacheEntry **new_hash_buckets, *new_entries;
00106 
00107     entries = 1L << new_size_log2;
00108 
00109     /*
00110     ** Expand the monitor-cache-entry free list
00111     */
00112     new_entries = (MonitorCacheEntry*)
00113         PR_CALLOC(entries * sizeof(MonitorCacheEntry));
00114     if (NULL == new_entries) return PR_FAILURE;
00115 
00116     /*
00117     ** Allocate system monitors for the new monitor cache entries. If we
00118     ** run out of system monitors, break out of the loop.
00119     */
00120     for (i = 0, added = 0, p = new_entries; i < entries; i++, p++, added++) {
00121         p->mon = PR_NewMonitor();
00122         if (!p->mon)
00123             break;
00124     }
00125     if (added != entries) {
00126         if (added == 0) {
00127             /* Totally out of system monitors. Lossage abounds */
00128             PR_DELETE(new_entries);
00129             return PR_FAILURE;
00130         }
00131 
00132         /*
00133         ** We were able to allocate some of the system monitors. Use
00134         ** realloc to shrink down the new_entries memory
00135         */
00136         p = (MonitorCacheEntry*)
00137             PR_REALLOC(new_entries, added * sizeof(MonitorCacheEntry));
00138         if (p == 0) {
00139             /*
00140             ** Total lossage. We just leaked a bunch of system monitors
00141             ** all over the floor. This should never ever happen.
00142             */
00143             PR_ASSERT(p != 0);
00144             return PR_FAILURE;
00145         }
00146     }
00147 
00148     /*
00149     ** Now that we have allocated all of the system monitors, build up
00150     ** the new free list. We can just update the free_list because we own
00151     ** the mcache-lock and we aren't calling anyone who might want to use
00152     ** it.
00153     */
00154     for (i = 0, p = new_entries; i < added - 1; i++, p++)
00155         p->next = p + 1;
00156     p->next = free_entries;
00157     free_entries = new_entries;
00158     num_free_entries += added;
00159 
00160     /* Try to expand the hash table */
00161     new_hash_buckets = (MonitorCacheEntry**)
00162         PR_CALLOC(entries * sizeof(MonitorCacheEntry*));
00163     if (NULL == new_hash_buckets) {
00164         /*
00165         ** Partial lossage. In this situation we don't get any more hash
00166         ** buckets, which just means that the table lookups will take
00167         ** longer. This is bad, but not fatal
00168         */
00169         PR_LOG(_pr_cmon_lm, PR_LOG_WARNING,
00170                ("unable to grow monitor cache hash buckets"));
00171         return PR_SUCCESS;
00172     }
00173 
00174     /*
00175     ** Compute new hash mask value. This value is used to mask an address
00176     ** until it's bits are in the right spot for indexing into the hash
00177     ** table.
00178     */
00179     hash_mask = entries - 1;
00180 
00181     /*
00182     ** Expand the hash table. We have to rehash everything in the old
00183     ** table into the new table.
00184     */
00185     old_hash_buckets = hash_buckets;
00186     old_num_hash_buckets = num_hash_buckets;
00187     for (i = 0; i < old_num_hash_buckets; i++) {
00188         p = old_hash_buckets[i];
00189         while (p) {
00190             MonitorCacheEntry *next = p->next;
00191 
00192             /* Hash based on new table size, and then put p in the new table */
00193             PRUintn hash = HASH(p->address);
00194             p->next = new_hash_buckets[hash];
00195             new_hash_buckets[hash] = p;
00196 
00197             p = next;
00198         }
00199     }
00200 
00201     /*
00202     ** Switch over to new hash table and THEN call free of the old
00203     ** table. Since free might re-enter _pr_mcache_lock, things would
00204     ** break terribly if it used the old hash table.
00205     */
00206     hash_buckets = new_hash_buckets;
00207     num_hash_buckets = entries;
00208     num_hash_buckets_log2 = new_size_log2;
00209     PR_DELETE(old_hash_buckets);
00210 
00211     PR_LOG(_pr_cmon_lm, PR_LOG_NOTICE,
00212            ("expanded monitor cache to %d (buckets %d)",
00213             num_free_entries, entries));
00214 
00215     return PR_SUCCESS;
00216 }  /* ExpandMonitorCache */
00217 
00218 /*
00219 ** Lookup a monitor cache entry by address. Return a pointer to the
00220 ** pointer to the monitor cache entry on success, null on failure.
00221 */
00222 static MonitorCacheEntry **LookupMonitorCacheEntry(void *address)
00223 {
00224     PRUintn hash;
00225     MonitorCacheEntry **pp, *p;
00226 
00227     hash = HASH(address);
00228     pp = hash_buckets + hash;
00229     while ((p = *pp) != 0) {
00230         if (p->address == address) {
00231             if (p->cacheEntryCount > 0)
00232                 return pp;
00233             return NULL;
00234         }
00235         pp = &p->next;
00236     }
00237     return NULL;
00238 }
00239 
00240 /*
00241 ** Try to create a new cached monitor. If it's already in the cache,
00242 ** great - return it. Otherwise get a new free cache entry and set it
00243 ** up. If the cache free space is getting low, expand the cache.
00244 */
00245 static PRMonitor *CreateMonitor(void *address)
00246 {
00247     PRUintn hash;
00248     MonitorCacheEntry **pp, *p;
00249 
00250     hash = HASH(address);
00251     pp = hash_buckets + hash;
00252     while ((p = *pp) != 0) {
00253         if (p->address == address) goto gotit;
00254 
00255         pp = &p->next;
00256     }
00257 
00258     /* Expand the monitor cache if we have run out of free slots in the table */
00259     if (num_free_entries < FREE_THRESHOLD) {
00260         /* Expand monitor cache */
00261 
00262         /*
00263         ** This function is called with the lock held. So what's the 'expanding'
00264         ** boolean all about? Seems a bit redundant.
00265         */
00266         if (!expanding) {
00267             PRStatus rv;
00268 
00269             expanding = PR_TRUE;
00270             rv = ExpandMonitorCache(num_hash_buckets_log2 + 1);
00271             expanding = PR_FALSE;
00272             if (PR_FAILURE == rv)  return NULL;
00273 
00274             /* redo the hash because it'll be different now */
00275             hash = HASH(address);
00276         } else {
00277             /*
00278             ** We are in process of expanding and we need a cache
00279             ** monitor.  Make sure we have enough!
00280             */
00281             PR_ASSERT(num_free_entries > 0);
00282         }
00283     }
00284 
00285     /* Make a new monitor */
00286     p = free_entries;
00287     free_entries = p->next;
00288     num_free_entries--;
00289     if (OnMonitorRecycle && p->address)
00290         OnMonitorRecycle(p->address);
00291     p->address = address;
00292     p->next = hash_buckets[hash];
00293     hash_buckets[hash] = p;
00294     PR_ASSERT(p->cacheEntryCount == 0);
00295 
00296   gotit:
00297     p->cacheEntryCount++;
00298     return p->mon;
00299 }
00300 
00301 /*
00302 ** Initialize the monitor cache
00303 */
00304 void _PR_InitCMon(void)
00305 {
00306     _PR_NEW_LOCK_MCACHE();
00307     ExpandMonitorCache(3);
00308     _pr_mcache_ready = 1;
00309 }
00310 
00311 /*
00312 ** Create monitor for address. Don't enter the monitor while we have the
00313 ** mcache locked because we might block!
00314 */
00315 PR_IMPLEMENT(PRMonitor*) PR_CEnterMonitor(void *address)
00316 {
00317     PRMonitor *mon;
00318 
00319     if (!_pr_initialized) _PR_ImplicitInitialization();
00320 
00321     _PR_LOCK_MCACHE();
00322     mon = CreateMonitor(address);
00323     _PR_UNLOCK_MCACHE();
00324 
00325     if (!mon) return NULL;
00326 
00327     PR_EnterMonitor(mon);
00328     return mon;
00329 }
00330 
00331 PR_IMPLEMENT(PRStatus) PR_CExitMonitor(void *address)
00332 {
00333     MonitorCacheEntry **pp, *p;
00334     PRStatus status = PR_SUCCESS;
00335 
00336     _PR_LOCK_MCACHE();
00337     pp = LookupMonitorCacheEntry(address);
00338     if (pp != NULL) {
00339         p = *pp;
00340         if (--p->cacheEntryCount == 0) {
00341             /*
00342             ** Nobody is using the system monitor. Put it on the cached free
00343             ** list. We are safe from somebody trying to use it because we
00344             ** have the mcache locked.
00345             */
00346             p->address = 0;             /* defensive move */
00347             *pp = p->next;              /* unlink from hash_buckets */
00348             p->next = free_entries;     /* link into free list */
00349             free_entries = p;
00350             num_free_entries++;         /* count it as free */
00351         }
00352         status = PR_ExitMonitor(p->mon);
00353     } else {
00354         status = PR_FAILURE;
00355     }
00356     _PR_UNLOCK_MCACHE();
00357 
00358     return status;
00359 }
00360 
00361 PR_IMPLEMENT(PRStatus) PR_CWait(void *address, PRIntervalTime ticks)
00362 {
00363     MonitorCacheEntry **pp;
00364     PRMonitor *mon;
00365 
00366     _PR_LOCK_MCACHE();
00367     pp = LookupMonitorCacheEntry(address);
00368     mon = pp ? ((*pp)->mon) : NULL;
00369     _PR_UNLOCK_MCACHE();
00370 
00371     if (mon == NULL)
00372         return PR_FAILURE;
00373     return PR_Wait(mon, ticks);
00374 }
00375 
00376 PR_IMPLEMENT(PRStatus) PR_CNotify(void *address)
00377 {
00378     MonitorCacheEntry **pp;
00379     PRMonitor *mon;
00380 
00381     _PR_LOCK_MCACHE();
00382     pp = LookupMonitorCacheEntry(address);
00383     mon = pp ? ((*pp)->mon) : NULL;
00384     _PR_UNLOCK_MCACHE();
00385 
00386     if (mon == NULL)
00387         return PR_FAILURE;
00388     return PR_Notify(mon);
00389 }
00390 
00391 PR_IMPLEMENT(PRStatus) PR_CNotifyAll(void *address)
00392 {
00393     MonitorCacheEntry **pp;
00394     PRMonitor *mon;
00395 
00396     _PR_LOCK_MCACHE();
00397     pp = LookupMonitorCacheEntry(address);
00398     mon = pp ? ((*pp)->mon) : NULL;
00399     _PR_UNLOCK_MCACHE();
00400 
00401     if (mon == NULL)
00402         return PR_FAILURE;
00403     return PR_NotifyAll(mon);
00404 }
00405 
00406 PR_IMPLEMENT(void)
00407 PR_CSetOnMonitorRecycle(void (*callback)(void *address))
00408 {
00409     OnMonitorRecycle = callback;
00410 }