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