Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
michael@0 | 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "primpl.h" |
michael@0 | 7 | |
michael@0 | 8 | #include <stdlib.h> |
michael@0 | 9 | #include <stddef.h> |
michael@0 | 10 | |
michael@0 | 11 | /* Lock used to lock the monitor cache */ |
michael@0 | 12 | #ifdef _PR_NO_PREEMPT |
michael@0 | 13 | #define _PR_NEW_LOCK_MCACHE() |
michael@0 | 14 | #define _PR_DESTROY_LOCK_MCACHE() |
michael@0 | 15 | #define _PR_LOCK_MCACHE() |
michael@0 | 16 | #define _PR_UNLOCK_MCACHE() |
michael@0 | 17 | #else |
michael@0 | 18 | #ifdef _PR_LOCAL_THREADS_ONLY |
michael@0 | 19 | #define _PR_NEW_LOCK_MCACHE() |
michael@0 | 20 | #define _PR_DESTROY_LOCK_MCACHE() |
michael@0 | 21 | #define _PR_LOCK_MCACHE() { PRIntn _is; _PR_INTSOFF(_is) |
michael@0 | 22 | #define _PR_UNLOCK_MCACHE() _PR_INTSON(_is); } |
michael@0 | 23 | #else |
michael@0 | 24 | PRLock *_pr_mcacheLock; |
michael@0 | 25 | #define _PR_NEW_LOCK_MCACHE() (_pr_mcacheLock = PR_NewLock()) |
michael@0 | 26 | #define _PR_DESTROY_LOCK_MCACHE() \ |
michael@0 | 27 | PR_BEGIN_MACRO \ |
michael@0 | 28 | if (_pr_mcacheLock) { \ |
michael@0 | 29 | PR_DestroyLock(_pr_mcacheLock); \ |
michael@0 | 30 | _pr_mcacheLock = NULL; \ |
michael@0 | 31 | } \ |
michael@0 | 32 | PR_END_MACRO |
michael@0 | 33 | #define _PR_LOCK_MCACHE() PR_Lock(_pr_mcacheLock) |
michael@0 | 34 | #define _PR_UNLOCK_MCACHE() PR_Unlock(_pr_mcacheLock) |
michael@0 | 35 | #endif |
michael@0 | 36 | #endif |
michael@0 | 37 | |
michael@0 | 38 | /************************************************************************/ |
michael@0 | 39 | |
michael@0 | 40 | typedef struct MonitorCacheEntryStr MonitorCacheEntry; |
michael@0 | 41 | |
michael@0 | 42 | struct MonitorCacheEntryStr { |
michael@0 | 43 | MonitorCacheEntry* next; |
michael@0 | 44 | void* address; |
michael@0 | 45 | PRMonitor* mon; |
michael@0 | 46 | long cacheEntryCount; |
michael@0 | 47 | }; |
michael@0 | 48 | |
michael@0 | 49 | /* |
michael@0 | 50 | ** An array of MonitorCacheEntry's, plus a pointer to link these |
michael@0 | 51 | ** arrays together. |
michael@0 | 52 | */ |
michael@0 | 53 | |
michael@0 | 54 | typedef struct MonitorCacheEntryBlockStr MonitorCacheEntryBlock; |
michael@0 | 55 | |
michael@0 | 56 | struct MonitorCacheEntryBlockStr { |
michael@0 | 57 | MonitorCacheEntryBlock* next; |
michael@0 | 58 | MonitorCacheEntry entries[1]; |
michael@0 | 59 | }; |
michael@0 | 60 | |
michael@0 | 61 | static PRUint32 hash_mask; |
michael@0 | 62 | static PRUintn num_hash_buckets; |
michael@0 | 63 | static PRUintn num_hash_buckets_log2; |
michael@0 | 64 | static MonitorCacheEntry **hash_buckets; |
michael@0 | 65 | static MonitorCacheEntry *free_entries; |
michael@0 | 66 | static PRUintn num_free_entries; |
michael@0 | 67 | static PRBool expanding; |
michael@0 | 68 | static MonitorCacheEntryBlock *mcache_blocks; |
michael@0 | 69 | |
michael@0 | 70 | static void (*OnMonitorRecycle)(void *address); |
michael@0 | 71 | |
michael@0 | 72 | #define HASH(address) \ |
michael@0 | 73 | ((PRUint32) ( ((PRUptrdiff)(address) >> 2) ^ \ |
michael@0 | 74 | ((PRUptrdiff)(address) >> 10) ) \ |
michael@0 | 75 | & hash_mask) |
michael@0 | 76 | |
michael@0 | 77 | /* |
michael@0 | 78 | ** Expand the monitor cache. This grows the hash buckets and allocates a |
michael@0 | 79 | ** new chunk of cache entries and throws them on the free list. We keep |
michael@0 | 80 | ** as many hash buckets as there are entries. |
michael@0 | 81 | ** |
michael@0 | 82 | ** Because we call malloc and malloc may need the monitor cache, we must |
michael@0 | 83 | ** ensure that there are several free monitor cache entries available for |
michael@0 | 84 | ** malloc to get. FREE_THRESHOLD is used to prevent monitor cache |
michael@0 | 85 | ** starvation during monitor cache expansion. |
michael@0 | 86 | */ |
michael@0 | 87 | |
michael@0 | 88 | #define FREE_THRESHOLD 5 |
michael@0 | 89 | |
michael@0 | 90 | static PRStatus ExpandMonitorCache(PRUintn new_size_log2) |
michael@0 | 91 | { |
michael@0 | 92 | MonitorCacheEntry **old_hash_buckets, *p; |
michael@0 | 93 | PRUintn i, entries, old_num_hash_buckets, added; |
michael@0 | 94 | MonitorCacheEntry **new_hash_buckets; |
michael@0 | 95 | MonitorCacheEntryBlock *new_block; |
michael@0 | 96 | |
michael@0 | 97 | entries = 1L << new_size_log2; |
michael@0 | 98 | |
michael@0 | 99 | /* |
michael@0 | 100 | ** Expand the monitor-cache-entry free list |
michael@0 | 101 | */ |
michael@0 | 102 | new_block = (MonitorCacheEntryBlock*) |
michael@0 | 103 | PR_CALLOC(sizeof(MonitorCacheEntryBlock) |
michael@0 | 104 | + (entries - 1) * sizeof(MonitorCacheEntry)); |
michael@0 | 105 | if (NULL == new_block) return PR_FAILURE; |
michael@0 | 106 | |
michael@0 | 107 | /* |
michael@0 | 108 | ** Allocate system monitors for the new monitor cache entries. If we |
michael@0 | 109 | ** run out of system monitors, break out of the loop. |
michael@0 | 110 | */ |
michael@0 | 111 | for (i = 0, p = new_block->entries; i < entries; i++, p++) { |
michael@0 | 112 | p->mon = PR_NewMonitor(); |
michael@0 | 113 | if (!p->mon) |
michael@0 | 114 | break; |
michael@0 | 115 | } |
michael@0 | 116 | added = i; |
michael@0 | 117 | if (added != entries) { |
michael@0 | 118 | MonitorCacheEntryBlock *realloc_block; |
michael@0 | 119 | |
michael@0 | 120 | if (added == 0) { |
michael@0 | 121 | /* Totally out of system monitors. Lossage abounds */ |
michael@0 | 122 | PR_DELETE(new_block); |
michael@0 | 123 | return PR_FAILURE; |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | /* |
michael@0 | 127 | ** We were able to allocate some of the system monitors. Use |
michael@0 | 128 | ** realloc to shrink down the new_block memory. If that fails, |
michael@0 | 129 | ** carry on with the too-large new_block. |
michael@0 | 130 | */ |
michael@0 | 131 | realloc_block = (MonitorCacheEntryBlock*) |
michael@0 | 132 | PR_REALLOC(new_block, sizeof(MonitorCacheEntryBlock) |
michael@0 | 133 | + (added - 1) * sizeof(MonitorCacheEntry)); |
michael@0 | 134 | if (realloc_block) |
michael@0 | 135 | new_block = realloc_block; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | /* |
michael@0 | 139 | ** Now that we have allocated all of the system monitors, build up |
michael@0 | 140 | ** the new free list. We can just update the free_list because we own |
michael@0 | 141 | ** the mcache-lock and we aren't calling anyone who might want to use |
michael@0 | 142 | ** it. |
michael@0 | 143 | */ |
michael@0 | 144 | for (i = 0, p = new_block->entries; i < added - 1; i++, p++) |
michael@0 | 145 | p->next = p + 1; |
michael@0 | 146 | p->next = free_entries; |
michael@0 | 147 | free_entries = new_block->entries; |
michael@0 | 148 | num_free_entries += added; |
michael@0 | 149 | new_block->next = mcache_blocks; |
michael@0 | 150 | mcache_blocks = new_block; |
michael@0 | 151 | |
michael@0 | 152 | /* Try to expand the hash table */ |
michael@0 | 153 | new_hash_buckets = (MonitorCacheEntry**) |
michael@0 | 154 | PR_CALLOC(entries * sizeof(MonitorCacheEntry*)); |
michael@0 | 155 | if (NULL == new_hash_buckets) { |
michael@0 | 156 | /* |
michael@0 | 157 | ** Partial lossage. In this situation we don't get any more hash |
michael@0 | 158 | ** buckets, which just means that the table lookups will take |
michael@0 | 159 | ** longer. This is bad, but not fatal |
michael@0 | 160 | */ |
michael@0 | 161 | PR_LOG(_pr_cmon_lm, PR_LOG_WARNING, |
michael@0 | 162 | ("unable to grow monitor cache hash buckets")); |
michael@0 | 163 | return PR_SUCCESS; |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | /* |
michael@0 | 167 | ** Compute new hash mask value. This value is used to mask an address |
michael@0 | 168 | ** until it's bits are in the right spot for indexing into the hash |
michael@0 | 169 | ** table. |
michael@0 | 170 | */ |
michael@0 | 171 | hash_mask = entries - 1; |
michael@0 | 172 | |
michael@0 | 173 | /* |
michael@0 | 174 | ** Expand the hash table. We have to rehash everything in the old |
michael@0 | 175 | ** table into the new table. |
michael@0 | 176 | */ |
michael@0 | 177 | old_hash_buckets = hash_buckets; |
michael@0 | 178 | old_num_hash_buckets = num_hash_buckets; |
michael@0 | 179 | for (i = 0; i < old_num_hash_buckets; i++) { |
michael@0 | 180 | p = old_hash_buckets[i]; |
michael@0 | 181 | while (p) { |
michael@0 | 182 | MonitorCacheEntry *next = p->next; |
michael@0 | 183 | |
michael@0 | 184 | /* Hash based on new table size, and then put p in the new table */ |
michael@0 | 185 | PRUintn hash = HASH(p->address); |
michael@0 | 186 | p->next = new_hash_buckets[hash]; |
michael@0 | 187 | new_hash_buckets[hash] = p; |
michael@0 | 188 | |
michael@0 | 189 | p = next; |
michael@0 | 190 | } |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | /* |
michael@0 | 194 | ** Switch over to new hash table and THEN call free of the old |
michael@0 | 195 | ** table. Since free might re-enter _pr_mcache_lock, things would |
michael@0 | 196 | ** break terribly if it used the old hash table. |
michael@0 | 197 | */ |
michael@0 | 198 | hash_buckets = new_hash_buckets; |
michael@0 | 199 | num_hash_buckets = entries; |
michael@0 | 200 | num_hash_buckets_log2 = new_size_log2; |
michael@0 | 201 | PR_DELETE(old_hash_buckets); |
michael@0 | 202 | |
michael@0 | 203 | PR_LOG(_pr_cmon_lm, PR_LOG_NOTICE, |
michael@0 | 204 | ("expanded monitor cache to %d (buckets %d)", |
michael@0 | 205 | num_free_entries, entries)); |
michael@0 | 206 | |
michael@0 | 207 | return PR_SUCCESS; |
michael@0 | 208 | } /* ExpandMonitorCache */ |
michael@0 | 209 | |
michael@0 | 210 | /* |
michael@0 | 211 | ** Lookup a monitor cache entry by address. Return a pointer to the |
michael@0 | 212 | ** pointer to the monitor cache entry on success, null on failure. |
michael@0 | 213 | */ |
michael@0 | 214 | static MonitorCacheEntry **LookupMonitorCacheEntry(void *address) |
michael@0 | 215 | { |
michael@0 | 216 | PRUintn hash; |
michael@0 | 217 | MonitorCacheEntry **pp, *p; |
michael@0 | 218 | |
michael@0 | 219 | hash = HASH(address); |
michael@0 | 220 | pp = hash_buckets + hash; |
michael@0 | 221 | while ((p = *pp) != 0) { |
michael@0 | 222 | if (p->address == address) { |
michael@0 | 223 | if (p->cacheEntryCount > 0) |
michael@0 | 224 | return pp; |
michael@0 | 225 | return NULL; |
michael@0 | 226 | } |
michael@0 | 227 | pp = &p->next; |
michael@0 | 228 | } |
michael@0 | 229 | return NULL; |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | /* |
michael@0 | 233 | ** Try to create a new cached monitor. If it's already in the cache, |
michael@0 | 234 | ** great - return it. Otherwise get a new free cache entry and set it |
michael@0 | 235 | ** up. If the cache free space is getting low, expand the cache. |
michael@0 | 236 | */ |
michael@0 | 237 | static PRMonitor *CreateMonitor(void *address) |
michael@0 | 238 | { |
michael@0 | 239 | PRUintn hash; |
michael@0 | 240 | MonitorCacheEntry **pp, *p; |
michael@0 | 241 | |
michael@0 | 242 | hash = HASH(address); |
michael@0 | 243 | pp = hash_buckets + hash; |
michael@0 | 244 | while ((p = *pp) != 0) { |
michael@0 | 245 | if (p->address == address) goto gotit; |
michael@0 | 246 | |
michael@0 | 247 | pp = &p->next; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | /* Expand the monitor cache if we have run out of free slots in the table */ |
michael@0 | 251 | if (num_free_entries < FREE_THRESHOLD) { |
michael@0 | 252 | /* Expand monitor cache */ |
michael@0 | 253 | |
michael@0 | 254 | /* |
michael@0 | 255 | ** This function is called with the lock held. So what's the 'expanding' |
michael@0 | 256 | ** boolean all about? Seems a bit redundant. |
michael@0 | 257 | */ |
michael@0 | 258 | if (!expanding) { |
michael@0 | 259 | PRStatus rv; |
michael@0 | 260 | |
michael@0 | 261 | expanding = PR_TRUE; |
michael@0 | 262 | rv = ExpandMonitorCache(num_hash_buckets_log2 + 1); |
michael@0 | 263 | expanding = PR_FALSE; |
michael@0 | 264 | if (PR_FAILURE == rv) return NULL; |
michael@0 | 265 | |
michael@0 | 266 | /* redo the hash because it'll be different now */ |
michael@0 | 267 | hash = HASH(address); |
michael@0 | 268 | } else { |
michael@0 | 269 | /* |
michael@0 | 270 | ** We are in process of expanding and we need a cache |
michael@0 | 271 | ** monitor. Make sure we have enough! |
michael@0 | 272 | */ |
michael@0 | 273 | PR_ASSERT(num_free_entries > 0); |
michael@0 | 274 | } |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | /* Make a new monitor */ |
michael@0 | 278 | p = free_entries; |
michael@0 | 279 | free_entries = p->next; |
michael@0 | 280 | num_free_entries--; |
michael@0 | 281 | if (OnMonitorRecycle && p->address) |
michael@0 | 282 | OnMonitorRecycle(p->address); |
michael@0 | 283 | p->address = address; |
michael@0 | 284 | p->next = hash_buckets[hash]; |
michael@0 | 285 | hash_buckets[hash] = p; |
michael@0 | 286 | PR_ASSERT(p->cacheEntryCount == 0); |
michael@0 | 287 | |
michael@0 | 288 | gotit: |
michael@0 | 289 | p->cacheEntryCount++; |
michael@0 | 290 | return p->mon; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | /* |
michael@0 | 294 | ** Initialize the monitor cache |
michael@0 | 295 | */ |
michael@0 | 296 | void _PR_InitCMon(void) |
michael@0 | 297 | { |
michael@0 | 298 | _PR_NEW_LOCK_MCACHE(); |
michael@0 | 299 | ExpandMonitorCache(3); |
michael@0 | 300 | } |
michael@0 | 301 | |
michael@0 | 302 | /* |
michael@0 | 303 | ** Destroy the monitor cache |
michael@0 | 304 | */ |
michael@0 | 305 | void _PR_CleanupCMon(void) |
michael@0 | 306 | { |
michael@0 | 307 | _PR_DESTROY_LOCK_MCACHE(); |
michael@0 | 308 | |
michael@0 | 309 | while (free_entries) { |
michael@0 | 310 | PR_DestroyMonitor(free_entries->mon); |
michael@0 | 311 | free_entries = free_entries->next; |
michael@0 | 312 | } |
michael@0 | 313 | num_free_entries = 0; |
michael@0 | 314 | |
michael@0 | 315 | while (mcache_blocks) { |
michael@0 | 316 | MonitorCacheEntryBlock *block; |
michael@0 | 317 | |
michael@0 | 318 | block = mcache_blocks; |
michael@0 | 319 | mcache_blocks = block->next; |
michael@0 | 320 | PR_DELETE(block); |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | PR_DELETE(hash_buckets); |
michael@0 | 324 | hash_mask = 0; |
michael@0 | 325 | num_hash_buckets = 0; |
michael@0 | 326 | num_hash_buckets_log2 = 0; |
michael@0 | 327 | |
michael@0 | 328 | expanding = PR_FALSE; |
michael@0 | 329 | OnMonitorRecycle = NULL; |
michael@0 | 330 | } |
michael@0 | 331 | |
michael@0 | 332 | /* |
michael@0 | 333 | ** Create monitor for address. Don't enter the monitor while we have the |
michael@0 | 334 | ** mcache locked because we might block! |
michael@0 | 335 | */ |
michael@0 | 336 | PR_IMPLEMENT(PRMonitor*) PR_CEnterMonitor(void *address) |
michael@0 | 337 | { |
michael@0 | 338 | PRMonitor *mon; |
michael@0 | 339 | |
michael@0 | 340 | if (!_pr_initialized) _PR_ImplicitInitialization(); |
michael@0 | 341 | |
michael@0 | 342 | _PR_LOCK_MCACHE(); |
michael@0 | 343 | mon = CreateMonitor(address); |
michael@0 | 344 | _PR_UNLOCK_MCACHE(); |
michael@0 | 345 | |
michael@0 | 346 | if (!mon) return NULL; |
michael@0 | 347 | |
michael@0 | 348 | PR_EnterMonitor(mon); |
michael@0 | 349 | return mon; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | PR_IMPLEMENT(PRStatus) PR_CExitMonitor(void *address) |
michael@0 | 353 | { |
michael@0 | 354 | MonitorCacheEntry **pp, *p; |
michael@0 | 355 | PRStatus status = PR_SUCCESS; |
michael@0 | 356 | |
michael@0 | 357 | _PR_LOCK_MCACHE(); |
michael@0 | 358 | pp = LookupMonitorCacheEntry(address); |
michael@0 | 359 | if (pp != NULL) { |
michael@0 | 360 | p = *pp; |
michael@0 | 361 | if (--p->cacheEntryCount == 0) { |
michael@0 | 362 | /* |
michael@0 | 363 | ** Nobody is using the system monitor. Put it on the cached free |
michael@0 | 364 | ** list. We are safe from somebody trying to use it because we |
michael@0 | 365 | ** have the mcache locked. |
michael@0 | 366 | */ |
michael@0 | 367 | p->address = 0; /* defensive move */ |
michael@0 | 368 | *pp = p->next; /* unlink from hash_buckets */ |
michael@0 | 369 | p->next = free_entries; /* link into free list */ |
michael@0 | 370 | free_entries = p; |
michael@0 | 371 | num_free_entries++; /* count it as free */ |
michael@0 | 372 | } |
michael@0 | 373 | status = PR_ExitMonitor(p->mon); |
michael@0 | 374 | } else { |
michael@0 | 375 | status = PR_FAILURE; |
michael@0 | 376 | } |
michael@0 | 377 | _PR_UNLOCK_MCACHE(); |
michael@0 | 378 | |
michael@0 | 379 | return status; |
michael@0 | 380 | } |
michael@0 | 381 | |
michael@0 | 382 | PR_IMPLEMENT(PRStatus) PR_CWait(void *address, PRIntervalTime ticks) |
michael@0 | 383 | { |
michael@0 | 384 | MonitorCacheEntry **pp; |
michael@0 | 385 | PRMonitor *mon; |
michael@0 | 386 | |
michael@0 | 387 | _PR_LOCK_MCACHE(); |
michael@0 | 388 | pp = LookupMonitorCacheEntry(address); |
michael@0 | 389 | mon = pp ? ((*pp)->mon) : NULL; |
michael@0 | 390 | _PR_UNLOCK_MCACHE(); |
michael@0 | 391 | |
michael@0 | 392 | if (mon == NULL) |
michael@0 | 393 | return PR_FAILURE; |
michael@0 | 394 | return PR_Wait(mon, ticks); |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | PR_IMPLEMENT(PRStatus) PR_CNotify(void *address) |
michael@0 | 398 | { |
michael@0 | 399 | MonitorCacheEntry **pp; |
michael@0 | 400 | PRMonitor *mon; |
michael@0 | 401 | |
michael@0 | 402 | _PR_LOCK_MCACHE(); |
michael@0 | 403 | pp = LookupMonitorCacheEntry(address); |
michael@0 | 404 | mon = pp ? ((*pp)->mon) : NULL; |
michael@0 | 405 | _PR_UNLOCK_MCACHE(); |
michael@0 | 406 | |
michael@0 | 407 | if (mon == NULL) |
michael@0 | 408 | return PR_FAILURE; |
michael@0 | 409 | return PR_Notify(mon); |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | PR_IMPLEMENT(PRStatus) PR_CNotifyAll(void *address) |
michael@0 | 413 | { |
michael@0 | 414 | MonitorCacheEntry **pp; |
michael@0 | 415 | PRMonitor *mon; |
michael@0 | 416 | |
michael@0 | 417 | _PR_LOCK_MCACHE(); |
michael@0 | 418 | pp = LookupMonitorCacheEntry(address); |
michael@0 | 419 | mon = pp ? ((*pp)->mon) : NULL; |
michael@0 | 420 | _PR_UNLOCK_MCACHE(); |
michael@0 | 421 | |
michael@0 | 422 | if (mon == NULL) |
michael@0 | 423 | return PR_FAILURE; |
michael@0 | 424 | return PR_NotifyAll(mon); |
michael@0 | 425 | } |
michael@0 | 426 | |
michael@0 | 427 | PR_IMPLEMENT(void) |
michael@0 | 428 | PR_CSetOnMonitorRecycle(void (*callback)(void *address)) |
michael@0 | 429 | { |
michael@0 | 430 | OnMonitorRecycle = callback; |
michael@0 | 431 | } |