mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-06-16 13:38:44 +00:00
411 lines
12 KiB
C
411 lines
12 KiB
C
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
|
/* ***** 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 the Netscape Portable Runtime (NSPR).
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is
|
||
|
* Netscape Communications Corporation.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 1998-2000
|
||
|
* 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 "primpl.h"
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <stddef.h>
|
||
|
|
||
|
/* Lock used to lock the monitor cache */
|
||
|
#ifdef _PR_NO_PREEMPT
|
||
|
#define _PR_NEW_LOCK_MCACHE()
|
||
|
#define _PR_LOCK_MCACHE()
|
||
|
#define _PR_UNLOCK_MCACHE()
|
||
|
#else
|
||
|
#ifdef _PR_LOCAL_THREADS_ONLY
|
||
|
#define _PR_NEW_LOCK_MCACHE()
|
||
|
#define _PR_LOCK_MCACHE() { PRIntn _is; _PR_INTSOFF(_is)
|
||
|
#define _PR_UNLOCK_MCACHE() _PR_INTSON(_is); }
|
||
|
#else
|
||
|
PRLock *_pr_mcacheLock;
|
||
|
#define _PR_NEW_LOCK_MCACHE() (_pr_mcacheLock = PR_NewLock())
|
||
|
#define _PR_LOCK_MCACHE() PR_Lock(_pr_mcacheLock)
|
||
|
#define _PR_UNLOCK_MCACHE() PR_Unlock(_pr_mcacheLock)
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/************************************************************************/
|
||
|
|
||
|
typedef struct MonitorCacheEntryStr MonitorCacheEntry;
|
||
|
|
||
|
struct MonitorCacheEntryStr {
|
||
|
MonitorCacheEntry* next;
|
||
|
void* address;
|
||
|
PRMonitor* mon;
|
||
|
long cacheEntryCount;
|
||
|
};
|
||
|
|
||
|
static PRUint32 hash_mask;
|
||
|
static PRUintn num_hash_buckets;
|
||
|
static PRUintn num_hash_buckets_log2;
|
||
|
static MonitorCacheEntry **hash_buckets;
|
||
|
static MonitorCacheEntry *free_entries;
|
||
|
static PRUintn num_free_entries;
|
||
|
static PRBool expanding;
|
||
|
int _pr_mcache_ready;
|
||
|
|
||
|
static void (*OnMonitorRecycle)(void *address);
|
||
|
|
||
|
#define HASH(address) \
|
||
|
((PRUint32) ( ((PRUptrdiff)(address) >> 2) ^ \
|
||
|
((PRUptrdiff)(address) >> 10) ) \
|
||
|
& hash_mask)
|
||
|
|
||
|
/*
|
||
|
** Expand the monitor cache. This grows the hash buckets and allocates a
|
||
|
** new chunk of cache entries and throws them on the free list. We keep
|
||
|
** as many hash buckets as there are entries.
|
||
|
**
|
||
|
** Because we call malloc and malloc may need the monitor cache, we must
|
||
|
** ensure that there are several free monitor cache entries available for
|
||
|
** malloc to get. FREE_THRESHOLD is used to prevent monitor cache
|
||
|
** starvation during monitor cache expansion.
|
||
|
*/
|
||
|
|
||
|
#define FREE_THRESHOLD 5
|
||
|
|
||
|
static PRStatus ExpandMonitorCache(PRUintn new_size_log2)
|
||
|
{
|
||
|
MonitorCacheEntry **old_hash_buckets, *p;
|
||
|
PRUintn i, entries, old_num_hash_buckets, added;
|
||
|
MonitorCacheEntry **new_hash_buckets, *new_entries;
|
||
|
|
||
|
entries = 1L << new_size_log2;
|
||
|
|
||
|
/*
|
||
|
** Expand the monitor-cache-entry free list
|
||
|
*/
|
||
|
new_entries = (MonitorCacheEntry*)
|
||
|
PR_CALLOC(entries * sizeof(MonitorCacheEntry));
|
||
|
if (NULL == new_entries) return PR_FAILURE;
|
||
|
|
||
|
/*
|
||
|
** Allocate system monitors for the new monitor cache entries. If we
|
||
|
** run out of system monitors, break out of the loop.
|
||
|
*/
|
||
|
for (i = 0, added = 0, p = new_entries; i < entries; i++, p++, added++) {
|
||
|
p->mon = PR_NewMonitor();
|
||
|
if (!p->mon)
|
||
|
break;
|
||
|
}
|
||
|
if (added != entries) {
|
||
|
if (added == 0) {
|
||
|
/* Totally out of system monitors. Lossage abounds */
|
||
|
PR_DELETE(new_entries);
|
||
|
return PR_FAILURE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** We were able to allocate some of the system monitors. Use
|
||
|
** realloc to shrink down the new_entries memory
|
||
|
*/
|
||
|
p = (MonitorCacheEntry*)
|
||
|
PR_REALLOC(new_entries, added * sizeof(MonitorCacheEntry));
|
||
|
if (p == 0) {
|
||
|
/*
|
||
|
** Total lossage. We just leaked a bunch of system monitors
|
||
|
** all over the floor. This should never ever happen.
|
||
|
*/
|
||
|
PR_ASSERT(p != 0);
|
||
|
return PR_FAILURE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Now that we have allocated all of the system monitors, build up
|
||
|
** the new free list. We can just update the free_list because we own
|
||
|
** the mcache-lock and we aren't calling anyone who might want to use
|
||
|
** it.
|
||
|
*/
|
||
|
for (i = 0, p = new_entries; i < added - 1; i++, p++)
|
||
|
p->next = p + 1;
|
||
|
p->next = free_entries;
|
||
|
free_entries = new_entries;
|
||
|
num_free_entries += added;
|
||
|
|
||
|
/* Try to expand the hash table */
|
||
|
new_hash_buckets = (MonitorCacheEntry**)
|
||
|
PR_CALLOC(entries * sizeof(MonitorCacheEntry*));
|
||
|
if (NULL == new_hash_buckets) {
|
||
|
/*
|
||
|
** Partial lossage. In this situation we don't get any more hash
|
||
|
** buckets, which just means that the table lookups will take
|
||
|
** longer. This is bad, but not fatal
|
||
|
*/
|
||
|
PR_LOG(_pr_cmon_lm, PR_LOG_WARNING,
|
||
|
("unable to grow monitor cache hash buckets"));
|
||
|
return PR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Compute new hash mask value. This value is used to mask an address
|
||
|
** until it's bits are in the right spot for indexing into the hash
|
||
|
** table.
|
||
|
*/
|
||
|
hash_mask = entries - 1;
|
||
|
|
||
|
/*
|
||
|
** Expand the hash table. We have to rehash everything in the old
|
||
|
** table into the new table.
|
||
|
*/
|
||
|
old_hash_buckets = hash_buckets;
|
||
|
old_num_hash_buckets = num_hash_buckets;
|
||
|
for (i = 0; i < old_num_hash_buckets; i++) {
|
||
|
p = old_hash_buckets[i];
|
||
|
while (p) {
|
||
|
MonitorCacheEntry *next = p->next;
|
||
|
|
||
|
/* Hash based on new table size, and then put p in the new table */
|
||
|
PRUintn hash = HASH(p->address);
|
||
|
p->next = new_hash_buckets[hash];
|
||
|
new_hash_buckets[hash] = p;
|
||
|
|
||
|
p = next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Switch over to new hash table and THEN call free of the old
|
||
|
** table. Since free might re-enter _pr_mcache_lock, things would
|
||
|
** break terribly if it used the old hash table.
|
||
|
*/
|
||
|
hash_buckets = new_hash_buckets;
|
||
|
num_hash_buckets = entries;
|
||
|
num_hash_buckets_log2 = new_size_log2;
|
||
|
PR_DELETE(old_hash_buckets);
|
||
|
|
||
|
PR_LOG(_pr_cmon_lm, PR_LOG_NOTICE,
|
||
|
("expanded monitor cache to %d (buckets %d)",
|
||
|
num_free_entries, entries));
|
||
|
|
||
|
return PR_SUCCESS;
|
||
|
} /* ExpandMonitorCache */
|
||
|
|
||
|
/*
|
||
|
** Lookup a monitor cache entry by address. Return a pointer to the
|
||
|
** pointer to the monitor cache entry on success, null on failure.
|
||
|
*/
|
||
|
static MonitorCacheEntry **LookupMonitorCacheEntry(void *address)
|
||
|
{
|
||
|
PRUintn hash;
|
||
|
MonitorCacheEntry **pp, *p;
|
||
|
|
||
|
hash = HASH(address);
|
||
|
pp = hash_buckets + hash;
|
||
|
while ((p = *pp) != 0) {
|
||
|
if (p->address == address) {
|
||
|
if (p->cacheEntryCount > 0)
|
||
|
return pp;
|
||
|
return NULL;
|
||
|
}
|
||
|
pp = &p->next;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Try to create a new cached monitor. If it's already in the cache,
|
||
|
** great - return it. Otherwise get a new free cache entry and set it
|
||
|
** up. If the cache free space is getting low, expand the cache.
|
||
|
*/
|
||
|
static PRMonitor *CreateMonitor(void *address)
|
||
|
{
|
||
|
PRUintn hash;
|
||
|
MonitorCacheEntry **pp, *p;
|
||
|
|
||
|
hash = HASH(address);
|
||
|
pp = hash_buckets + hash;
|
||
|
while ((p = *pp) != 0) {
|
||
|
if (p->address == address) goto gotit;
|
||
|
|
||
|
pp = &p->next;
|
||
|
}
|
||
|
|
||
|
/* Expand the monitor cache if we have run out of free slots in the table */
|
||
|
if (num_free_entries < FREE_THRESHOLD) {
|
||
|
/* Expand monitor cache */
|
||
|
|
||
|
/*
|
||
|
** This function is called with the lock held. So what's the 'expanding'
|
||
|
** boolean all about? Seems a bit redundant.
|
||
|
*/
|
||
|
if (!expanding) {
|
||
|
PRStatus rv;
|
||
|
|
||
|
expanding = PR_TRUE;
|
||
|
rv = ExpandMonitorCache(num_hash_buckets_log2 + 1);
|
||
|
expanding = PR_FALSE;
|
||
|
if (PR_FAILURE == rv) return NULL;
|
||
|
|
||
|
/* redo the hash because it'll be different now */
|
||
|
hash = HASH(address);
|
||
|
} else {
|
||
|
/*
|
||
|
** We are in process of expanding and we need a cache
|
||
|
** monitor. Make sure we have enough!
|
||
|
*/
|
||
|
PR_ASSERT(num_free_entries > 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Make a new monitor */
|
||
|
p = free_entries;
|
||
|
free_entries = p->next;
|
||
|
num_free_entries--;
|
||
|
if (OnMonitorRecycle && p->address)
|
||
|
OnMonitorRecycle(p->address);
|
||
|
p->address = address;
|
||
|
p->next = hash_buckets[hash];
|
||
|
hash_buckets[hash] = p;
|
||
|
PR_ASSERT(p->cacheEntryCount == 0);
|
||
|
|
||
|
gotit:
|
||
|
p->cacheEntryCount++;
|
||
|
return p->mon;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Initialize the monitor cache
|
||
|
*/
|
||
|
void _PR_InitCMon(void)
|
||
|
{
|
||
|
_PR_NEW_LOCK_MCACHE();
|
||
|
ExpandMonitorCache(3);
|
||
|
_pr_mcache_ready = 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Create monitor for address. Don't enter the monitor while we have the
|
||
|
** mcache locked because we might block!
|
||
|
*/
|
||
|
PR_IMPLEMENT(PRMonitor*) PR_CEnterMonitor(void *address)
|
||
|
{
|
||
|
PRMonitor *mon;
|
||
|
|
||
|
if (!_pr_initialized) _PR_ImplicitInitialization();
|
||
|
|
||
|
_PR_LOCK_MCACHE();
|
||
|
mon = CreateMonitor(address);
|
||
|
_PR_UNLOCK_MCACHE();
|
||
|
|
||
|
if (!mon) return NULL;
|
||
|
|
||
|
PR_EnterMonitor(mon);
|
||
|
return mon;
|
||
|
}
|
||
|
|
||
|
PR_IMPLEMENT(PRStatus) PR_CExitMonitor(void *address)
|
||
|
{
|
||
|
MonitorCacheEntry **pp, *p;
|
||
|
PRStatus status = PR_SUCCESS;
|
||
|
|
||
|
_PR_LOCK_MCACHE();
|
||
|
pp = LookupMonitorCacheEntry(address);
|
||
|
if (pp != NULL) {
|
||
|
p = *pp;
|
||
|
if (--p->cacheEntryCount == 0) {
|
||
|
/*
|
||
|
** Nobody is using the system monitor. Put it on the cached free
|
||
|
** list. We are safe from somebody trying to use it because we
|
||
|
** have the mcache locked.
|
||
|
*/
|
||
|
p->address = 0; /* defensive move */
|
||
|
*pp = p->next; /* unlink from hash_buckets */
|
||
|
p->next = free_entries; /* link into free list */
|
||
|
free_entries = p;
|
||
|
num_free_entries++; /* count it as free */
|
||
|
}
|
||
|
status = PR_ExitMonitor(p->mon);
|
||
|
} else {
|
||
|
status = PR_FAILURE;
|
||
|
}
|
||
|
_PR_UNLOCK_MCACHE();
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
PR_IMPLEMENT(PRStatus) PR_CWait(void *address, PRIntervalTime ticks)
|
||
|
{
|
||
|
MonitorCacheEntry **pp;
|
||
|
PRMonitor *mon;
|
||
|
|
||
|
_PR_LOCK_MCACHE();
|
||
|
pp = LookupMonitorCacheEntry(address);
|
||
|
mon = pp ? ((*pp)->mon) : NULL;
|
||
|
_PR_UNLOCK_MCACHE();
|
||
|
|
||
|
if (mon == NULL)
|
||
|
return PR_FAILURE;
|
||
|
return PR_Wait(mon, ticks);
|
||
|
}
|
||
|
|
||
|
PR_IMPLEMENT(PRStatus) PR_CNotify(void *address)
|
||
|
{
|
||
|
MonitorCacheEntry **pp;
|
||
|
PRMonitor *mon;
|
||
|
|
||
|
_PR_LOCK_MCACHE();
|
||
|
pp = LookupMonitorCacheEntry(address);
|
||
|
mon = pp ? ((*pp)->mon) : NULL;
|
||
|
_PR_UNLOCK_MCACHE();
|
||
|
|
||
|
if (mon == NULL)
|
||
|
return PR_FAILURE;
|
||
|
return PR_Notify(mon);
|
||
|
}
|
||
|
|
||
|
PR_IMPLEMENT(PRStatus) PR_CNotifyAll(void *address)
|
||
|
{
|
||
|
MonitorCacheEntry **pp;
|
||
|
PRMonitor *mon;
|
||
|
|
||
|
_PR_LOCK_MCACHE();
|
||
|
pp = LookupMonitorCacheEntry(address);
|
||
|
mon = pp ? ((*pp)->mon) : NULL;
|
||
|
_PR_UNLOCK_MCACHE();
|
||
|
|
||
|
if (mon == NULL)
|
||
|
return PR_FAILURE;
|
||
|
return PR_NotifyAll(mon);
|
||
|
}
|
||
|
|
||
|
PR_IMPLEMENT(void)
|
||
|
PR_CSetOnMonitorRecycle(void (*callback)(void *address))
|
||
|
{
|
||
|
OnMonitorRecycle = callback;
|
||
|
}
|