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