@@ -259,17 +259,17 @@ VALUE secondary_map_mutex = Qnil;
259
259
260
260
// Lambda that will GC entries from the secondary map that are no longer present
261
261
// in the primary map.
262
- VALUE gc_secondary_map = Qnil ;
262
+ VALUE gc_secondary_map_lambda = Qnil ;
263
263
ID length ;
264
264
265
265
extern VALUE weak_obj_cache ;
266
266
267
267
static void SecondaryMap_Init () {
268
268
rb_gc_register_address (& secondary_map );
269
- rb_gc_register_address (& gc_secondary_map );
269
+ rb_gc_register_address (& gc_secondary_map_lambda );
270
270
rb_gc_register_address (& secondary_map_mutex );
271
271
secondary_map = rb_hash_new ();
272
- gc_secondary_map = rb_eval_string (
272
+ gc_secondary_map_lambda = rb_eval_string (
273
273
"->(secondary, weak) {\n"
274
274
" secondary.delete_if { |k, v| !weak.key?(v) }\n"
275
275
"}\n" );
@@ -287,43 +287,61 @@ static void SecondaryMap_Init() {
287
287
// secondary map that are no longer present in the WeakMap. The logic of
288
288
// how often to perform this GC is an artbirary tuning parameter that
289
289
// represents a straightforward CPU/memory tradeoff.
290
+ //
291
+ // Requires: secondary_map_mutex is held.
290
292
static void SecondaryMap_MaybeGC () {
293
+ PBRUBY_ASSERT (rb_mutex_locked_p (secondary_map_mutex ) == Qtrue );
291
294
size_t weak_len = NUM2ULL (rb_funcall (weak_obj_cache , length , 0 ));
292
295
size_t secondary_len = RHASH_SIZE (secondary_map );
296
+ if (secondary_len < weak_len ) {
297
+ // Logically this case should not be possible: a valid entry cannot exist in
298
+ // the weak table unless there is a corresponding entry in the secondary
299
+ // table. It should *always* be the case that secondary_len >= weak_len.
300
+ //
301
+ // However ObjectSpace::WeakMap#length (and therefore weak_len) is
302
+ // unreliable: it overreports its true length by including non-live objects.
303
+ // However these non-live objects are not yielded in iteration, so we may
304
+ // have previously deleted them from the secondary map in a previous
305
+ // invocation of SecondaryMap_MaybeGC().
306
+ //
307
+ // In this case, we can't measure any waste, so we just return.
308
+ return ;
309
+ }
293
310
size_t waste = secondary_len - weak_len ;
294
- PBRUBY_ASSERT (secondary_len >= weak_len );
295
311
// GC if we could remove at least 2000 entries or 20% of the table size
296
312
// (whichever is greater). Since the cost of the GC pass is O(N), we
297
313
// want to make sure that we condition this on overall table size, to
298
314
// avoid O(N^2) CPU costs.
299
315
size_t threshold = PBRUBY_MAX (secondary_len * 0.2 , 2000 );
300
316
if (waste > threshold ) {
301
- rb_funcall (gc_secondary_map , rb_intern ("call" ), 2 , secondary_map , weak_obj_cache );
317
+ rb_funcall (gc_secondary_map_lambda , rb_intern ("call" ), 2 ,
318
+ secondary_map , weak_obj_cache );
302
319
}
303
320
}
304
321
305
- static VALUE SecondaryMap_Get (VALUE key ) {
322
+ // Requires: secondary_map_mutex is held by this thread iff create == true.
323
+ static VALUE SecondaryMap_Get (VALUE key , bool create ) {
324
+ PBRUBY_ASSERT (!create || rb_mutex_locked_p (secondary_map_mutex ) == Qtrue );
306
325
VALUE ret = rb_hash_lookup (secondary_map , key );
307
- if (ret == Qnil ) {
308
- rb_mutex_lock (secondary_map_mutex );
326
+ if (ret == Qnil && create ) {
309
327
SecondaryMap_MaybeGC ();
310
328
ret = rb_eval_string ("Object.new" );
311
329
rb_hash_aset (secondary_map , key , ret );
312
- rb_mutex_unlock (secondary_map_mutex );
313
330
}
314
331
return ret ;
315
332
}
316
333
317
334
#endif
318
335
319
- static VALUE ObjectCache_GetKey (const void * key ) {
336
+ // Requires: secondary_map_mutex is held by this thread iff create == true.
337
+ static VALUE ObjectCache_GetKey (const void * key , bool create ) {
320
338
char buf [sizeof (key )];
321
339
memcpy (& buf , & key , sizeof (key ));
322
340
intptr_t key_int = (intptr_t )key ;
323
341
PBRUBY_ASSERT ((key_int & 3 ) == 0 );
324
342
VALUE ret = LL2NUM (key_int >> 2 );
325
343
#if USE_SECONDARY_MAP
326
- ret = SecondaryMap_Get (ret );
344
+ ret = SecondaryMap_Get (ret , create );
327
345
#endif
328
346
return ret ;
329
347
}
@@ -347,14 +365,20 @@ static void ObjectCache_Init() {
347
365
348
366
void ObjectCache_Add (const void * key , VALUE val ) {
349
367
PBRUBY_ASSERT (ObjectCache_Get (key ) == Qnil );
350
- VALUE key_rb = ObjectCache_GetKey (key );
368
+ #if USE_SECONDARY_MAP
369
+ rb_mutex_lock (secondary_map_mutex );
370
+ #endif
371
+ VALUE key_rb = ObjectCache_GetKey (key , true);
351
372
rb_funcall (weak_obj_cache , item_set , 2 , key_rb , val );
373
+ #if USE_SECONDARY_MAP
374
+ rb_mutex_unlock (secondary_map_mutex );
375
+ #endif
352
376
PBRUBY_ASSERT (ObjectCache_Get (key ) == val );
353
377
}
354
378
355
379
// Returns the cached object for this key, if any. Otherwise returns Qnil.
356
380
VALUE ObjectCache_Get (const void * key ) {
357
- VALUE key_rb = ObjectCache_GetKey (key );
381
+ VALUE key_rb = ObjectCache_GetKey (key , false );
358
382
return rb_funcall (weak_obj_cache , item_get , 1 , key_rb );
359
383
}
360
384
0 commit comments