1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/nsprpub/pr/src/threads/prcmon.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,431 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "primpl.h" 1.10 + 1.11 +#include <stdlib.h> 1.12 +#include <stddef.h> 1.13 + 1.14 +/* Lock used to lock the monitor cache */ 1.15 +#ifdef _PR_NO_PREEMPT 1.16 +#define _PR_NEW_LOCK_MCACHE() 1.17 +#define _PR_DESTROY_LOCK_MCACHE() 1.18 +#define _PR_LOCK_MCACHE() 1.19 +#define _PR_UNLOCK_MCACHE() 1.20 +#else 1.21 +#ifdef _PR_LOCAL_THREADS_ONLY 1.22 +#define _PR_NEW_LOCK_MCACHE() 1.23 +#define _PR_DESTROY_LOCK_MCACHE() 1.24 +#define _PR_LOCK_MCACHE() { PRIntn _is; _PR_INTSOFF(_is) 1.25 +#define _PR_UNLOCK_MCACHE() _PR_INTSON(_is); } 1.26 +#else 1.27 +PRLock *_pr_mcacheLock; 1.28 +#define _PR_NEW_LOCK_MCACHE() (_pr_mcacheLock = PR_NewLock()) 1.29 +#define _PR_DESTROY_LOCK_MCACHE() \ 1.30 + PR_BEGIN_MACRO \ 1.31 + if (_pr_mcacheLock) { \ 1.32 + PR_DestroyLock(_pr_mcacheLock); \ 1.33 + _pr_mcacheLock = NULL; \ 1.34 + } \ 1.35 + PR_END_MACRO 1.36 +#define _PR_LOCK_MCACHE() PR_Lock(_pr_mcacheLock) 1.37 +#define _PR_UNLOCK_MCACHE() PR_Unlock(_pr_mcacheLock) 1.38 +#endif 1.39 +#endif 1.40 + 1.41 +/************************************************************************/ 1.42 + 1.43 +typedef struct MonitorCacheEntryStr MonitorCacheEntry; 1.44 + 1.45 +struct MonitorCacheEntryStr { 1.46 + MonitorCacheEntry* next; 1.47 + void* address; 1.48 + PRMonitor* mon; 1.49 + long cacheEntryCount; 1.50 +}; 1.51 + 1.52 +/* 1.53 +** An array of MonitorCacheEntry's, plus a pointer to link these 1.54 +** arrays together. 1.55 +*/ 1.56 + 1.57 +typedef struct MonitorCacheEntryBlockStr MonitorCacheEntryBlock; 1.58 + 1.59 +struct MonitorCacheEntryBlockStr { 1.60 + MonitorCacheEntryBlock* next; 1.61 + MonitorCacheEntry entries[1]; 1.62 +}; 1.63 + 1.64 +static PRUint32 hash_mask; 1.65 +static PRUintn num_hash_buckets; 1.66 +static PRUintn num_hash_buckets_log2; 1.67 +static MonitorCacheEntry **hash_buckets; 1.68 +static MonitorCacheEntry *free_entries; 1.69 +static PRUintn num_free_entries; 1.70 +static PRBool expanding; 1.71 +static MonitorCacheEntryBlock *mcache_blocks; 1.72 + 1.73 +static void (*OnMonitorRecycle)(void *address); 1.74 + 1.75 +#define HASH(address) \ 1.76 + ((PRUint32) ( ((PRUptrdiff)(address) >> 2) ^ \ 1.77 + ((PRUptrdiff)(address) >> 10) ) \ 1.78 + & hash_mask) 1.79 + 1.80 +/* 1.81 +** Expand the monitor cache. This grows the hash buckets and allocates a 1.82 +** new chunk of cache entries and throws them on the free list. We keep 1.83 +** as many hash buckets as there are entries. 1.84 +** 1.85 +** Because we call malloc and malloc may need the monitor cache, we must 1.86 +** ensure that there are several free monitor cache entries available for 1.87 +** malloc to get. FREE_THRESHOLD is used to prevent monitor cache 1.88 +** starvation during monitor cache expansion. 1.89 +*/ 1.90 + 1.91 +#define FREE_THRESHOLD 5 1.92 + 1.93 +static PRStatus ExpandMonitorCache(PRUintn new_size_log2) 1.94 +{ 1.95 + MonitorCacheEntry **old_hash_buckets, *p; 1.96 + PRUintn i, entries, old_num_hash_buckets, added; 1.97 + MonitorCacheEntry **new_hash_buckets; 1.98 + MonitorCacheEntryBlock *new_block; 1.99 + 1.100 + entries = 1L << new_size_log2; 1.101 + 1.102 + /* 1.103 + ** Expand the monitor-cache-entry free list 1.104 + */ 1.105 + new_block = (MonitorCacheEntryBlock*) 1.106 + PR_CALLOC(sizeof(MonitorCacheEntryBlock) 1.107 + + (entries - 1) * sizeof(MonitorCacheEntry)); 1.108 + if (NULL == new_block) return PR_FAILURE; 1.109 + 1.110 + /* 1.111 + ** Allocate system monitors for the new monitor cache entries. If we 1.112 + ** run out of system monitors, break out of the loop. 1.113 + */ 1.114 + for (i = 0, p = new_block->entries; i < entries; i++, p++) { 1.115 + p->mon = PR_NewMonitor(); 1.116 + if (!p->mon) 1.117 + break; 1.118 + } 1.119 + added = i; 1.120 + if (added != entries) { 1.121 + MonitorCacheEntryBlock *realloc_block; 1.122 + 1.123 + if (added == 0) { 1.124 + /* Totally out of system monitors. Lossage abounds */ 1.125 + PR_DELETE(new_block); 1.126 + return PR_FAILURE; 1.127 + } 1.128 + 1.129 + /* 1.130 + ** We were able to allocate some of the system monitors. Use 1.131 + ** realloc to shrink down the new_block memory. If that fails, 1.132 + ** carry on with the too-large new_block. 1.133 + */ 1.134 + realloc_block = (MonitorCacheEntryBlock*) 1.135 + PR_REALLOC(new_block, sizeof(MonitorCacheEntryBlock) 1.136 + + (added - 1) * sizeof(MonitorCacheEntry)); 1.137 + if (realloc_block) 1.138 + new_block = realloc_block; 1.139 + } 1.140 + 1.141 + /* 1.142 + ** Now that we have allocated all of the system monitors, build up 1.143 + ** the new free list. We can just update the free_list because we own 1.144 + ** the mcache-lock and we aren't calling anyone who might want to use 1.145 + ** it. 1.146 + */ 1.147 + for (i = 0, p = new_block->entries; i < added - 1; i++, p++) 1.148 + p->next = p + 1; 1.149 + p->next = free_entries; 1.150 + free_entries = new_block->entries; 1.151 + num_free_entries += added; 1.152 + new_block->next = mcache_blocks; 1.153 + mcache_blocks = new_block; 1.154 + 1.155 + /* Try to expand the hash table */ 1.156 + new_hash_buckets = (MonitorCacheEntry**) 1.157 + PR_CALLOC(entries * sizeof(MonitorCacheEntry*)); 1.158 + if (NULL == new_hash_buckets) { 1.159 + /* 1.160 + ** Partial lossage. In this situation we don't get any more hash 1.161 + ** buckets, which just means that the table lookups will take 1.162 + ** longer. This is bad, but not fatal 1.163 + */ 1.164 + PR_LOG(_pr_cmon_lm, PR_LOG_WARNING, 1.165 + ("unable to grow monitor cache hash buckets")); 1.166 + return PR_SUCCESS; 1.167 + } 1.168 + 1.169 + /* 1.170 + ** Compute new hash mask value. This value is used to mask an address 1.171 + ** until it's bits are in the right spot for indexing into the hash 1.172 + ** table. 1.173 + */ 1.174 + hash_mask = entries - 1; 1.175 + 1.176 + /* 1.177 + ** Expand the hash table. We have to rehash everything in the old 1.178 + ** table into the new table. 1.179 + */ 1.180 + old_hash_buckets = hash_buckets; 1.181 + old_num_hash_buckets = num_hash_buckets; 1.182 + for (i = 0; i < old_num_hash_buckets; i++) { 1.183 + p = old_hash_buckets[i]; 1.184 + while (p) { 1.185 + MonitorCacheEntry *next = p->next; 1.186 + 1.187 + /* Hash based on new table size, and then put p in the new table */ 1.188 + PRUintn hash = HASH(p->address); 1.189 + p->next = new_hash_buckets[hash]; 1.190 + new_hash_buckets[hash] = p; 1.191 + 1.192 + p = next; 1.193 + } 1.194 + } 1.195 + 1.196 + /* 1.197 + ** Switch over to new hash table and THEN call free of the old 1.198 + ** table. Since free might re-enter _pr_mcache_lock, things would 1.199 + ** break terribly if it used the old hash table. 1.200 + */ 1.201 + hash_buckets = new_hash_buckets; 1.202 + num_hash_buckets = entries; 1.203 + num_hash_buckets_log2 = new_size_log2; 1.204 + PR_DELETE(old_hash_buckets); 1.205 + 1.206 + PR_LOG(_pr_cmon_lm, PR_LOG_NOTICE, 1.207 + ("expanded monitor cache to %d (buckets %d)", 1.208 + num_free_entries, entries)); 1.209 + 1.210 + return PR_SUCCESS; 1.211 +} /* ExpandMonitorCache */ 1.212 + 1.213 +/* 1.214 +** Lookup a monitor cache entry by address. Return a pointer to the 1.215 +** pointer to the monitor cache entry on success, null on failure. 1.216 +*/ 1.217 +static MonitorCacheEntry **LookupMonitorCacheEntry(void *address) 1.218 +{ 1.219 + PRUintn hash; 1.220 + MonitorCacheEntry **pp, *p; 1.221 + 1.222 + hash = HASH(address); 1.223 + pp = hash_buckets + hash; 1.224 + while ((p = *pp) != 0) { 1.225 + if (p->address == address) { 1.226 + if (p->cacheEntryCount > 0) 1.227 + return pp; 1.228 + return NULL; 1.229 + } 1.230 + pp = &p->next; 1.231 + } 1.232 + return NULL; 1.233 +} 1.234 + 1.235 +/* 1.236 +** Try to create a new cached monitor. If it's already in the cache, 1.237 +** great - return it. Otherwise get a new free cache entry and set it 1.238 +** up. If the cache free space is getting low, expand the cache. 1.239 +*/ 1.240 +static PRMonitor *CreateMonitor(void *address) 1.241 +{ 1.242 + PRUintn hash; 1.243 + MonitorCacheEntry **pp, *p; 1.244 + 1.245 + hash = HASH(address); 1.246 + pp = hash_buckets + hash; 1.247 + while ((p = *pp) != 0) { 1.248 + if (p->address == address) goto gotit; 1.249 + 1.250 + pp = &p->next; 1.251 + } 1.252 + 1.253 + /* Expand the monitor cache if we have run out of free slots in the table */ 1.254 + if (num_free_entries < FREE_THRESHOLD) { 1.255 + /* Expand monitor cache */ 1.256 + 1.257 + /* 1.258 + ** This function is called with the lock held. So what's the 'expanding' 1.259 + ** boolean all about? Seems a bit redundant. 1.260 + */ 1.261 + if (!expanding) { 1.262 + PRStatus rv; 1.263 + 1.264 + expanding = PR_TRUE; 1.265 + rv = ExpandMonitorCache(num_hash_buckets_log2 + 1); 1.266 + expanding = PR_FALSE; 1.267 + if (PR_FAILURE == rv) return NULL; 1.268 + 1.269 + /* redo the hash because it'll be different now */ 1.270 + hash = HASH(address); 1.271 + } else { 1.272 + /* 1.273 + ** We are in process of expanding and we need a cache 1.274 + ** monitor. Make sure we have enough! 1.275 + */ 1.276 + PR_ASSERT(num_free_entries > 0); 1.277 + } 1.278 + } 1.279 + 1.280 + /* Make a new monitor */ 1.281 + p = free_entries; 1.282 + free_entries = p->next; 1.283 + num_free_entries--; 1.284 + if (OnMonitorRecycle && p->address) 1.285 + OnMonitorRecycle(p->address); 1.286 + p->address = address; 1.287 + p->next = hash_buckets[hash]; 1.288 + hash_buckets[hash] = p; 1.289 + PR_ASSERT(p->cacheEntryCount == 0); 1.290 + 1.291 + gotit: 1.292 + p->cacheEntryCount++; 1.293 + return p->mon; 1.294 +} 1.295 + 1.296 +/* 1.297 +** Initialize the monitor cache 1.298 +*/ 1.299 +void _PR_InitCMon(void) 1.300 +{ 1.301 + _PR_NEW_LOCK_MCACHE(); 1.302 + ExpandMonitorCache(3); 1.303 +} 1.304 + 1.305 +/* 1.306 +** Destroy the monitor cache 1.307 +*/ 1.308 +void _PR_CleanupCMon(void) 1.309 +{ 1.310 + _PR_DESTROY_LOCK_MCACHE(); 1.311 + 1.312 + while (free_entries) { 1.313 + PR_DestroyMonitor(free_entries->mon); 1.314 + free_entries = free_entries->next; 1.315 + } 1.316 + num_free_entries = 0; 1.317 + 1.318 + while (mcache_blocks) { 1.319 + MonitorCacheEntryBlock *block; 1.320 + 1.321 + block = mcache_blocks; 1.322 + mcache_blocks = block->next; 1.323 + PR_DELETE(block); 1.324 + } 1.325 + 1.326 + PR_DELETE(hash_buckets); 1.327 + hash_mask = 0; 1.328 + num_hash_buckets = 0; 1.329 + num_hash_buckets_log2 = 0; 1.330 + 1.331 + expanding = PR_FALSE; 1.332 + OnMonitorRecycle = NULL; 1.333 +} 1.334 + 1.335 +/* 1.336 +** Create monitor for address. Don't enter the monitor while we have the 1.337 +** mcache locked because we might block! 1.338 +*/ 1.339 +PR_IMPLEMENT(PRMonitor*) PR_CEnterMonitor(void *address) 1.340 +{ 1.341 + PRMonitor *mon; 1.342 + 1.343 + if (!_pr_initialized) _PR_ImplicitInitialization(); 1.344 + 1.345 + _PR_LOCK_MCACHE(); 1.346 + mon = CreateMonitor(address); 1.347 + _PR_UNLOCK_MCACHE(); 1.348 + 1.349 + if (!mon) return NULL; 1.350 + 1.351 + PR_EnterMonitor(mon); 1.352 + return mon; 1.353 +} 1.354 + 1.355 +PR_IMPLEMENT(PRStatus) PR_CExitMonitor(void *address) 1.356 +{ 1.357 + MonitorCacheEntry **pp, *p; 1.358 + PRStatus status = PR_SUCCESS; 1.359 + 1.360 + _PR_LOCK_MCACHE(); 1.361 + pp = LookupMonitorCacheEntry(address); 1.362 + if (pp != NULL) { 1.363 + p = *pp; 1.364 + if (--p->cacheEntryCount == 0) { 1.365 + /* 1.366 + ** Nobody is using the system monitor. Put it on the cached free 1.367 + ** list. We are safe from somebody trying to use it because we 1.368 + ** have the mcache locked. 1.369 + */ 1.370 + p->address = 0; /* defensive move */ 1.371 + *pp = p->next; /* unlink from hash_buckets */ 1.372 + p->next = free_entries; /* link into free list */ 1.373 + free_entries = p; 1.374 + num_free_entries++; /* count it as free */ 1.375 + } 1.376 + status = PR_ExitMonitor(p->mon); 1.377 + } else { 1.378 + status = PR_FAILURE; 1.379 + } 1.380 + _PR_UNLOCK_MCACHE(); 1.381 + 1.382 + return status; 1.383 +} 1.384 + 1.385 +PR_IMPLEMENT(PRStatus) PR_CWait(void *address, PRIntervalTime ticks) 1.386 +{ 1.387 + MonitorCacheEntry **pp; 1.388 + PRMonitor *mon; 1.389 + 1.390 + _PR_LOCK_MCACHE(); 1.391 + pp = LookupMonitorCacheEntry(address); 1.392 + mon = pp ? ((*pp)->mon) : NULL; 1.393 + _PR_UNLOCK_MCACHE(); 1.394 + 1.395 + if (mon == NULL) 1.396 + return PR_FAILURE; 1.397 + return PR_Wait(mon, ticks); 1.398 +} 1.399 + 1.400 +PR_IMPLEMENT(PRStatus) PR_CNotify(void *address) 1.401 +{ 1.402 + MonitorCacheEntry **pp; 1.403 + PRMonitor *mon; 1.404 + 1.405 + _PR_LOCK_MCACHE(); 1.406 + pp = LookupMonitorCacheEntry(address); 1.407 + mon = pp ? ((*pp)->mon) : NULL; 1.408 + _PR_UNLOCK_MCACHE(); 1.409 + 1.410 + if (mon == NULL) 1.411 + return PR_FAILURE; 1.412 + return PR_Notify(mon); 1.413 +} 1.414 + 1.415 +PR_IMPLEMENT(PRStatus) PR_CNotifyAll(void *address) 1.416 +{ 1.417 + MonitorCacheEntry **pp; 1.418 + PRMonitor *mon; 1.419 + 1.420 + _PR_LOCK_MCACHE(); 1.421 + pp = LookupMonitorCacheEntry(address); 1.422 + mon = pp ? ((*pp)->mon) : NULL; 1.423 + _PR_UNLOCK_MCACHE(); 1.424 + 1.425 + if (mon == NULL) 1.426 + return PR_FAILURE; 1.427 + return PR_NotifyAll(mon); 1.428 +} 1.429 + 1.430 +PR_IMPLEMENT(void) 1.431 +PR_CSetOnMonitorRecycle(void (*callback)(void *address)) 1.432 +{ 1.433 + OnMonitorRecycle = callback; 1.434 +}