michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "primpl.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: /* Lock used to lock the monitor cache */ michael@0: #ifdef _PR_NO_PREEMPT michael@0: #define _PR_NEW_LOCK_MCACHE() michael@0: #define _PR_DESTROY_LOCK_MCACHE() michael@0: #define _PR_LOCK_MCACHE() michael@0: #define _PR_UNLOCK_MCACHE() michael@0: #else michael@0: #ifdef _PR_LOCAL_THREADS_ONLY michael@0: #define _PR_NEW_LOCK_MCACHE() michael@0: #define _PR_DESTROY_LOCK_MCACHE() michael@0: #define _PR_LOCK_MCACHE() { PRIntn _is; _PR_INTSOFF(_is) michael@0: #define _PR_UNLOCK_MCACHE() _PR_INTSON(_is); } michael@0: #else michael@0: PRLock *_pr_mcacheLock; michael@0: #define _PR_NEW_LOCK_MCACHE() (_pr_mcacheLock = PR_NewLock()) michael@0: #define _PR_DESTROY_LOCK_MCACHE() \ michael@0: PR_BEGIN_MACRO \ michael@0: if (_pr_mcacheLock) { \ michael@0: PR_DestroyLock(_pr_mcacheLock); \ michael@0: _pr_mcacheLock = NULL; \ michael@0: } \ michael@0: PR_END_MACRO michael@0: #define _PR_LOCK_MCACHE() PR_Lock(_pr_mcacheLock) michael@0: #define _PR_UNLOCK_MCACHE() PR_Unlock(_pr_mcacheLock) michael@0: #endif michael@0: #endif michael@0: michael@0: /************************************************************************/ michael@0: michael@0: typedef struct MonitorCacheEntryStr MonitorCacheEntry; michael@0: michael@0: struct MonitorCacheEntryStr { michael@0: MonitorCacheEntry* next; michael@0: void* address; michael@0: PRMonitor* mon; michael@0: long cacheEntryCount; michael@0: }; michael@0: michael@0: /* michael@0: ** An array of MonitorCacheEntry's, plus a pointer to link these michael@0: ** arrays together. michael@0: */ michael@0: michael@0: typedef struct MonitorCacheEntryBlockStr MonitorCacheEntryBlock; michael@0: michael@0: struct MonitorCacheEntryBlockStr { michael@0: MonitorCacheEntryBlock* next; michael@0: MonitorCacheEntry entries[1]; michael@0: }; michael@0: michael@0: static PRUint32 hash_mask; michael@0: static PRUintn num_hash_buckets; michael@0: static PRUintn num_hash_buckets_log2; michael@0: static MonitorCacheEntry **hash_buckets; michael@0: static MonitorCacheEntry *free_entries; michael@0: static PRUintn num_free_entries; michael@0: static PRBool expanding; michael@0: static MonitorCacheEntryBlock *mcache_blocks; michael@0: michael@0: static void (*OnMonitorRecycle)(void *address); michael@0: michael@0: #define HASH(address) \ michael@0: ((PRUint32) ( ((PRUptrdiff)(address) >> 2) ^ \ michael@0: ((PRUptrdiff)(address) >> 10) ) \ michael@0: & hash_mask) michael@0: michael@0: /* michael@0: ** Expand the monitor cache. This grows the hash buckets and allocates a michael@0: ** new chunk of cache entries and throws them on the free list. We keep michael@0: ** as many hash buckets as there are entries. michael@0: ** michael@0: ** Because we call malloc and malloc may need the monitor cache, we must michael@0: ** ensure that there are several free monitor cache entries available for michael@0: ** malloc to get. FREE_THRESHOLD is used to prevent monitor cache michael@0: ** starvation during monitor cache expansion. michael@0: */ michael@0: michael@0: #define FREE_THRESHOLD 5 michael@0: michael@0: static PRStatus ExpandMonitorCache(PRUintn new_size_log2) michael@0: { michael@0: MonitorCacheEntry **old_hash_buckets, *p; michael@0: PRUintn i, entries, old_num_hash_buckets, added; michael@0: MonitorCacheEntry **new_hash_buckets; michael@0: MonitorCacheEntryBlock *new_block; michael@0: michael@0: entries = 1L << new_size_log2; michael@0: michael@0: /* michael@0: ** Expand the monitor-cache-entry free list michael@0: */ michael@0: new_block = (MonitorCacheEntryBlock*) michael@0: PR_CALLOC(sizeof(MonitorCacheEntryBlock) michael@0: + (entries - 1) * sizeof(MonitorCacheEntry)); michael@0: if (NULL == new_block) return PR_FAILURE; michael@0: michael@0: /* michael@0: ** Allocate system monitors for the new monitor cache entries. If we michael@0: ** run out of system monitors, break out of the loop. michael@0: */ michael@0: for (i = 0, p = new_block->entries; i < entries; i++, p++) { michael@0: p->mon = PR_NewMonitor(); michael@0: if (!p->mon) michael@0: break; michael@0: } michael@0: added = i; michael@0: if (added != entries) { michael@0: MonitorCacheEntryBlock *realloc_block; michael@0: michael@0: if (added == 0) { michael@0: /* Totally out of system monitors. Lossage abounds */ michael@0: PR_DELETE(new_block); michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: /* michael@0: ** We were able to allocate some of the system monitors. Use michael@0: ** realloc to shrink down the new_block memory. If that fails, michael@0: ** carry on with the too-large new_block. michael@0: */ michael@0: realloc_block = (MonitorCacheEntryBlock*) michael@0: PR_REALLOC(new_block, sizeof(MonitorCacheEntryBlock) michael@0: + (added - 1) * sizeof(MonitorCacheEntry)); michael@0: if (realloc_block) michael@0: new_block = realloc_block; michael@0: } michael@0: michael@0: /* michael@0: ** Now that we have allocated all of the system monitors, build up michael@0: ** the new free list. We can just update the free_list because we own michael@0: ** the mcache-lock and we aren't calling anyone who might want to use michael@0: ** it. michael@0: */ michael@0: for (i = 0, p = new_block->entries; i < added - 1; i++, p++) michael@0: p->next = p + 1; michael@0: p->next = free_entries; michael@0: free_entries = new_block->entries; michael@0: num_free_entries += added; michael@0: new_block->next = mcache_blocks; michael@0: mcache_blocks = new_block; michael@0: michael@0: /* Try to expand the hash table */ michael@0: new_hash_buckets = (MonitorCacheEntry**) michael@0: PR_CALLOC(entries * sizeof(MonitorCacheEntry*)); michael@0: if (NULL == new_hash_buckets) { michael@0: /* michael@0: ** Partial lossage. In this situation we don't get any more hash michael@0: ** buckets, which just means that the table lookups will take michael@0: ** longer. This is bad, but not fatal michael@0: */ michael@0: PR_LOG(_pr_cmon_lm, PR_LOG_WARNING, michael@0: ("unable to grow monitor cache hash buckets")); michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: /* michael@0: ** Compute new hash mask value. This value is used to mask an address michael@0: ** until it's bits are in the right spot for indexing into the hash michael@0: ** table. michael@0: */ michael@0: hash_mask = entries - 1; michael@0: michael@0: /* michael@0: ** Expand the hash table. We have to rehash everything in the old michael@0: ** table into the new table. michael@0: */ michael@0: old_hash_buckets = hash_buckets; michael@0: old_num_hash_buckets = num_hash_buckets; michael@0: for (i = 0; i < old_num_hash_buckets; i++) { michael@0: p = old_hash_buckets[i]; michael@0: while (p) { michael@0: MonitorCacheEntry *next = p->next; michael@0: michael@0: /* Hash based on new table size, and then put p in the new table */ michael@0: PRUintn hash = HASH(p->address); michael@0: p->next = new_hash_buckets[hash]; michael@0: new_hash_buckets[hash] = p; michael@0: michael@0: p = next; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: ** Switch over to new hash table and THEN call free of the old michael@0: ** table. Since free might re-enter _pr_mcache_lock, things would michael@0: ** break terribly if it used the old hash table. michael@0: */ michael@0: hash_buckets = new_hash_buckets; michael@0: num_hash_buckets = entries; michael@0: num_hash_buckets_log2 = new_size_log2; michael@0: PR_DELETE(old_hash_buckets); michael@0: michael@0: PR_LOG(_pr_cmon_lm, PR_LOG_NOTICE, michael@0: ("expanded monitor cache to %d (buckets %d)", michael@0: num_free_entries, entries)); michael@0: michael@0: return PR_SUCCESS; michael@0: } /* ExpandMonitorCache */ michael@0: michael@0: /* michael@0: ** Lookup a monitor cache entry by address. Return a pointer to the michael@0: ** pointer to the monitor cache entry on success, null on failure. michael@0: */ michael@0: static MonitorCacheEntry **LookupMonitorCacheEntry(void *address) michael@0: { michael@0: PRUintn hash; michael@0: MonitorCacheEntry **pp, *p; michael@0: michael@0: hash = HASH(address); michael@0: pp = hash_buckets + hash; michael@0: while ((p = *pp) != 0) { michael@0: if (p->address == address) { michael@0: if (p->cacheEntryCount > 0) michael@0: return pp; michael@0: return NULL; michael@0: } michael@0: pp = &p->next; michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: /* michael@0: ** Try to create a new cached monitor. If it's already in the cache, michael@0: ** great - return it. Otherwise get a new free cache entry and set it michael@0: ** up. If the cache free space is getting low, expand the cache. michael@0: */ michael@0: static PRMonitor *CreateMonitor(void *address) michael@0: { michael@0: PRUintn hash; michael@0: MonitorCacheEntry **pp, *p; michael@0: michael@0: hash = HASH(address); michael@0: pp = hash_buckets + hash; michael@0: while ((p = *pp) != 0) { michael@0: if (p->address == address) goto gotit; michael@0: michael@0: pp = &p->next; michael@0: } michael@0: michael@0: /* Expand the monitor cache if we have run out of free slots in the table */ michael@0: if (num_free_entries < FREE_THRESHOLD) { michael@0: /* Expand monitor cache */ michael@0: michael@0: /* michael@0: ** This function is called with the lock held. So what's the 'expanding' michael@0: ** boolean all about? Seems a bit redundant. michael@0: */ michael@0: if (!expanding) { michael@0: PRStatus rv; michael@0: michael@0: expanding = PR_TRUE; michael@0: rv = ExpandMonitorCache(num_hash_buckets_log2 + 1); michael@0: expanding = PR_FALSE; michael@0: if (PR_FAILURE == rv) return NULL; michael@0: michael@0: /* redo the hash because it'll be different now */ michael@0: hash = HASH(address); michael@0: } else { michael@0: /* michael@0: ** We are in process of expanding and we need a cache michael@0: ** monitor. Make sure we have enough! michael@0: */ michael@0: PR_ASSERT(num_free_entries > 0); michael@0: } michael@0: } michael@0: michael@0: /* Make a new monitor */ michael@0: p = free_entries; michael@0: free_entries = p->next; michael@0: num_free_entries--; michael@0: if (OnMonitorRecycle && p->address) michael@0: OnMonitorRecycle(p->address); michael@0: p->address = address; michael@0: p->next = hash_buckets[hash]; michael@0: hash_buckets[hash] = p; michael@0: PR_ASSERT(p->cacheEntryCount == 0); michael@0: michael@0: gotit: michael@0: p->cacheEntryCount++; michael@0: return p->mon; michael@0: } michael@0: michael@0: /* michael@0: ** Initialize the monitor cache michael@0: */ michael@0: void _PR_InitCMon(void) michael@0: { michael@0: _PR_NEW_LOCK_MCACHE(); michael@0: ExpandMonitorCache(3); michael@0: } michael@0: michael@0: /* michael@0: ** Destroy the monitor cache michael@0: */ michael@0: void _PR_CleanupCMon(void) michael@0: { michael@0: _PR_DESTROY_LOCK_MCACHE(); michael@0: michael@0: while (free_entries) { michael@0: PR_DestroyMonitor(free_entries->mon); michael@0: free_entries = free_entries->next; michael@0: } michael@0: num_free_entries = 0; michael@0: michael@0: while (mcache_blocks) { michael@0: MonitorCacheEntryBlock *block; michael@0: michael@0: block = mcache_blocks; michael@0: mcache_blocks = block->next; michael@0: PR_DELETE(block); michael@0: } michael@0: michael@0: PR_DELETE(hash_buckets); michael@0: hash_mask = 0; michael@0: num_hash_buckets = 0; michael@0: num_hash_buckets_log2 = 0; michael@0: michael@0: expanding = PR_FALSE; michael@0: OnMonitorRecycle = NULL; michael@0: } michael@0: michael@0: /* michael@0: ** Create monitor for address. Don't enter the monitor while we have the michael@0: ** mcache locked because we might block! michael@0: */ michael@0: PR_IMPLEMENT(PRMonitor*) PR_CEnterMonitor(void *address) michael@0: { michael@0: PRMonitor *mon; michael@0: michael@0: if (!_pr_initialized) _PR_ImplicitInitialization(); michael@0: michael@0: _PR_LOCK_MCACHE(); michael@0: mon = CreateMonitor(address); michael@0: _PR_UNLOCK_MCACHE(); michael@0: michael@0: if (!mon) return NULL; michael@0: michael@0: PR_EnterMonitor(mon); michael@0: return mon; michael@0: } michael@0: michael@0: PR_IMPLEMENT(PRStatus) PR_CExitMonitor(void *address) michael@0: { michael@0: MonitorCacheEntry **pp, *p; michael@0: PRStatus status = PR_SUCCESS; michael@0: michael@0: _PR_LOCK_MCACHE(); michael@0: pp = LookupMonitorCacheEntry(address); michael@0: if (pp != NULL) { michael@0: p = *pp; michael@0: if (--p->cacheEntryCount == 0) { michael@0: /* michael@0: ** Nobody is using the system monitor. Put it on the cached free michael@0: ** list. We are safe from somebody trying to use it because we michael@0: ** have the mcache locked. michael@0: */ michael@0: p->address = 0; /* defensive move */ michael@0: *pp = p->next; /* unlink from hash_buckets */ michael@0: p->next = free_entries; /* link into free list */ michael@0: free_entries = p; michael@0: num_free_entries++; /* count it as free */ michael@0: } michael@0: status = PR_ExitMonitor(p->mon); michael@0: } else { michael@0: status = PR_FAILURE; michael@0: } michael@0: _PR_UNLOCK_MCACHE(); michael@0: michael@0: return status; michael@0: } michael@0: michael@0: PR_IMPLEMENT(PRStatus) PR_CWait(void *address, PRIntervalTime ticks) michael@0: { michael@0: MonitorCacheEntry **pp; michael@0: PRMonitor *mon; michael@0: michael@0: _PR_LOCK_MCACHE(); michael@0: pp = LookupMonitorCacheEntry(address); michael@0: mon = pp ? ((*pp)->mon) : NULL; michael@0: _PR_UNLOCK_MCACHE(); michael@0: michael@0: if (mon == NULL) michael@0: return PR_FAILURE; michael@0: return PR_Wait(mon, ticks); michael@0: } michael@0: michael@0: PR_IMPLEMENT(PRStatus) PR_CNotify(void *address) michael@0: { michael@0: MonitorCacheEntry **pp; michael@0: PRMonitor *mon; michael@0: michael@0: _PR_LOCK_MCACHE(); michael@0: pp = LookupMonitorCacheEntry(address); michael@0: mon = pp ? ((*pp)->mon) : NULL; michael@0: _PR_UNLOCK_MCACHE(); michael@0: michael@0: if (mon == NULL) michael@0: return PR_FAILURE; michael@0: return PR_Notify(mon); michael@0: } michael@0: michael@0: PR_IMPLEMENT(PRStatus) PR_CNotifyAll(void *address) michael@0: { michael@0: MonitorCacheEntry **pp; michael@0: PRMonitor *mon; michael@0: michael@0: _PR_LOCK_MCACHE(); michael@0: pp = LookupMonitorCacheEntry(address); michael@0: mon = pp ? ((*pp)->mon) : NULL; michael@0: _PR_UNLOCK_MCACHE(); michael@0: michael@0: if (mon == NULL) michael@0: return PR_FAILURE; michael@0: return PR_NotifyAll(mon); michael@0: } michael@0: michael@0: PR_IMPLEMENT(void) michael@0: PR_CSetOnMonitorRecycle(void (*callback)(void *address)) michael@0: { michael@0: OnMonitorRecycle = callback; michael@0: }