@@ -116,6 +116,8 @@ struct critnib_node {
116116struct critnib_leaf {
117117 word key ;
118118 void * value ;
119+ void * to_be_freed ;
120+ uint64_t ref_count ;
119121};
120122
121123struct critnib {
@@ -194,7 +196,12 @@ struct critnib *critnib_new(free_leaf_t cb_free_leaf, void *leaf_allocator) {
194196static void delete_node (struct critnib * c , struct critnib_node * __restrict n ) {
195197 if (is_leaf (n )) {
196198 if (c -> cb_free_leaf && to_leaf (n )) {
197- c -> cb_free_leaf (c -> leaf_allocator , (void * )to_leaf (n )-> value );
199+ if (to_leaf (n )-> value ) {
200+ c -> cb_free_leaf (c -> leaf_allocator , (void * )to_leaf (n )-> value );
201+ } else if (to_leaf (n )-> to_be_freed ) {
202+ c -> cb_free_leaf (c -> leaf_allocator ,
203+ (void * )to_leaf (n )-> to_be_freed );
204+ }
198205 }
199206 umf_ba_global_free (to_leaf (n ));
200207 } else {
@@ -233,8 +240,13 @@ void critnib_delete(struct critnib *c) {
233240 for (int i = 0 ; i < DELETED_LIFE ; i ++ ) {
234241 umf_ba_global_free (c -> pending_del_nodes [i ]);
235242 if (c -> cb_free_leaf && c -> pending_del_leaves [i ]) {
236- c -> cb_free_leaf (c -> leaf_allocator ,
237- (void * )c -> pending_del_leaves [i ]-> value );
243+ if (c -> pending_del_leaves [i ]-> value ) {
244+ c -> cb_free_leaf (c -> leaf_allocator ,
245+ (void * )c -> pending_del_leaves [i ]-> value );
246+ } else if (c -> pending_del_leaves [i ]-> to_be_freed ) {
247+ c -> cb_free_leaf (c -> leaf_allocator ,
248+ (void * )c -> pending_del_leaves [i ]-> to_be_freed );
249+ }
238250 }
239251 umf_ba_global_free (c -> pending_del_leaves [i ]);
240252 }
@@ -288,8 +300,17 @@ static void free_leaf(struct critnib *__restrict c,
288300 return ;
289301 }
290302
291- if (c -> cb_free_leaf && k && k -> value ) {
292- c -> cb_free_leaf (c -> leaf_allocator , (void * )k -> value );
303+ if (c -> cb_free_leaf && k ) {
304+ void * to_be_freed = NULL ;
305+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
306+ if (to_be_freed ) {
307+ if (k -> ref_count ) {
308+ LOG_DEBUG ("FREEING: ref_count = %llu, ptr = %p, value = %p" ,
309+ (unsigned long long )k -> ref_count , (void * )k -> key ,
310+ to_be_freed );
311+ }
312+ c -> cb_free_leaf (c -> leaf_allocator , to_be_freed );
313+ }
293314 }
294315
295316 utils_atomic_store_release_ptr ((void * * )& k -> value , c -> deleted_leaf );
@@ -334,8 +355,11 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
334355
335356 utils_annotate_memory_no_check (k , sizeof (struct critnib_leaf ));
336357
358+ utils_atomic_store_release_ptr (& k -> to_be_freed , 0 );
337359 utils_atomic_store_release_ptr ((void * * )& k -> key , (void * )key );
338360 utils_atomic_store_release_ptr ((void * * )& k -> value , value );
361+ // mark the leaf as valid (ref_count == 1)
362+ utils_atomic_store_release_u64 (& k -> ref_count , 1ULL );
339363
340364 struct critnib_node * kn = (void * )((word )k | 1 );
341365
@@ -370,9 +394,9 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
370394 word at = path ^ key ;
371395 if (!at ) {
372396 ASSERT (is_leaf (n ));
373- if (to_leaf (kn )-> value == value ) {
397+ if (to_leaf (kn )-> to_be_freed == value ) {
374398 // do not free the value
375- to_leaf (kn )-> value = NULL ;
399+ to_leaf (kn )-> to_be_freed = NULL ;
376400 }
377401 free_leaf (c , to_leaf (kn ));
378402
@@ -418,19 +442,35 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
418442/*
419443 * critnib_remove -- delete a key from the critnib structure, return its value
420444 */
421- void * critnib_remove (struct critnib * c , word key ) {
445+ void * critnib_remove (struct critnib * c , word key , void * * ref ) {
422446 struct critnib_leaf * k ;
423447 void * value = NULL ;
424448
449+ if (!ref ) {
450+ return NULL ;
451+ }
452+
425453 utils_mutex_lock (& c -> mutex );
426454
427455 struct critnib_node * n = c -> root ;
428456 if (!n ) {
429457 goto not_found ;
430458 }
431459
432- word del =
433- (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
460+ word del ;
461+ int i_del = 0 ;
462+ uint64_t ref_count = 0 ;
463+ do {
464+ del = (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
465+ k = c -> pending_del_leaves [del ];
466+ if (i_del ++ == DELETED_LIFE ) {
467+ break ;
468+ }
469+ if (k ) {
470+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
471+ }
472+ } while (k && (ref_count > 0 ));
473+
434474 free_node (c , c -> pending_del_nodes [del ]);
435475 free_leaf (c , c -> pending_del_leaves [del ]);
436476 c -> pending_del_nodes [del ] = NULL ;
@@ -491,13 +531,53 @@ void *critnib_remove(struct critnib *c, word key) {
491531
492532del_leaf :
493533 value = k -> value ;
534+ * ref = k ;
535+ utils_atomic_store_release_ptr (& k -> to_be_freed , value );
536+ utils_atomic_store_release_ptr ((void * * )& k -> value , NULL );
494537 c -> pending_del_leaves [del ] = k ;
495538
496539not_found :
497540 utils_mutex_unlock (& c -> mutex );
498541 return value ;
499542}
500543
544+ /*
545+ * critnib_release -- release a reference to a key
546+ */
547+ int critnib_release (struct critnib * c , void * ref ) {
548+ if (!c || !ref ) {
549+ return -1 ;
550+ }
551+
552+ struct critnib_leaf * k = (struct critnib_leaf * )ref ;
553+
554+ uint64_t ref_count ;
555+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
556+
557+ // check if the reference count is overflowed
558+ assert ((ref_count & (1ULL << 63 )) == 0 );
559+
560+ /* decrement the reference count */
561+ if (ref_count && (utils_atomic_decrement_u64 (& k -> ref_count ) == 0 )) {
562+ void * to_be_freed = NULL ;
563+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
564+ if (to_be_freed ) {
565+ utils_atomic_store_release_ptr (& k -> to_be_freed , NULL );
566+ if (to_be_freed && c -> cb_free_leaf ) {
567+ c -> cb_free_leaf (c -> leaf_allocator , to_be_freed );
568+ }
569+ }
570+ }
571+
572+ #ifndef NDEBUG
573+ // check if the reference count is overflowed
574+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
575+ assert ((ref_count & (1ULL << 63 )) == 0 );
576+ #endif
577+
578+ return 0 ;
579+ }
580+
501581/*
502582 * critnib_get -- query for a key ("==" match), returns value or NULL
503583 *
@@ -508,13 +588,17 @@ void *critnib_remove(struct critnib *c, word key) {
508588 * Counterintuitively, it's pointless to return the most current answer,
509589 * we need only one that was valid at any point after the call started.
510590 */
511- void * critnib_get (struct critnib * c , word key ) {
591+ void * critnib_get (struct critnib * c , word key , void * * ref ) {
592+ struct critnib_leaf * k ;
593+ struct critnib_node * n ;
512594 uint64_t wrs1 , wrs2 ;
513- void * res ;
595+ void * res = NULL ;
514596
515- do {
516- struct critnib_node * n ;
597+ if (!ref ) {
598+ return NULL ;
599+ }
517600
601+ do {
518602 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs1 );
519603 utils_atomic_load_acquire_ptr ((void * * )& c -> root , (void * * )& n );
520604
@@ -529,11 +613,25 @@ void *critnib_get(struct critnib *c, word key) {
529613 }
530614
531615 /* ... as we check it at the end. */
532- struct critnib_leaf * k = to_leaf (n );
616+ k = to_leaf (n );
533617 res = (n && k -> key == key ) ? k -> value : NULL ;
534618 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
535619 } while (wrs1 + DELETED_LIFE <= wrs2 );
536620
621+ if (res ) {
622+ uint64_t ref_count ;
623+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
624+ if (ref_count == 0 ) {
625+ return NULL ;
626+ }
627+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
628+ utils_atomic_decrement_u64 (& k -> ref_count );
629+ return NULL ;
630+ }
631+
632+ * ref = k ;
633+ }
634+
537635 return res ;
538636}
539637
@@ -645,19 +743,38 @@ static struct critnib_leaf *find_le(struct critnib_node *__restrict n,
645743 *
646744 * Same guarantees as critnib_get().
647745 */
648- void * critnib_find_le (struct critnib * c , word key ) {
746+ void * critnib_find_le (struct critnib * c , word key , void * * ref ) {
747+ struct critnib_leaf * k ;
649748 uint64_t wrs1 , wrs2 ;
650749 void * res ;
651750
751+ if (!ref ) {
752+ return NULL ;
753+ }
754+
652755 do {
653756 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs1 );
654757 struct critnib_node * n ; /* avoid a subtle TOCTOU */
655758 utils_atomic_load_acquire_ptr ((void * * )& c -> root , (void * * )& n );
656- struct critnib_leaf * k = n ? find_le (n , key ) : NULL ;
759+ k = n ? find_le (n , key ) : NULL ;
657760 res = k ? k -> value : NULL ;
658761 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
659762 } while (wrs1 + DELETED_LIFE <= wrs2 );
660763
764+ if (res ) {
765+ uint64_t ref_count ;
766+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
767+ if (ref_count == 0 ) {
768+ return NULL ;
769+ }
770+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
771+ utils_atomic_decrement_u64 (& k -> ref_count );
772+ return NULL ;
773+ }
774+
775+ * ref = k ;
776+ }
777+
661778 return res ;
662779}
663780
@@ -743,12 +860,16 @@ static struct critnib_leaf *find_ge(struct critnib_node *__restrict n,
743860 * critnib_find -- parametrized query, returns 1 if found
744861 */
745862int critnib_find (struct critnib * c , uintptr_t key , enum find_dir_t dir ,
746- uintptr_t * rkey , void * * rvalue ) {
863+ uintptr_t * rkey , void * * rvalue , void * * ref ) {
747864 uint64_t wrs1 , wrs2 ;
748865 struct critnib_leaf * k ;
749866 uintptr_t _rkey = (uintptr_t )0x0 ;
750867 void * * _rvalue = NULL ;
751868
869+ if (!ref ) {
870+ return 0 ;
871+ }
872+
752873 /* <42 ≡ ≤41 */
753874 if (dir < -1 ) {
754875 if (!key ) {
@@ -790,6 +911,18 @@ int critnib_find(struct critnib *c, uintptr_t key, enum find_dir_t dir,
790911 } while (wrs1 + DELETED_LIFE <= wrs2 );
791912
792913 if (k ) {
914+ uint64_t ref_count ;
915+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
916+ if (ref_count == 0 ) {
917+ return 0 ;
918+ }
919+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
920+ utils_atomic_decrement_u64 (& k -> ref_count );
921+ return 0 ;
922+ }
923+
924+ * ref = k ;
925+
793926 if (rkey ) {
794927 * rkey = _rkey ;
795928 }
0 commit comments