@@ -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,11 +300,7 @@ 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 );
293- }
294-
295- utils_atomic_store_release_ptr ((void * * )& k -> value , c -> deleted_leaf );
303+ utils_atomic_store_release_ptr (& k -> value , c -> deleted_leaf );
296304 utils_atomic_store_release_ptr ((void * * )& c -> deleted_leaf , k );
297305}
298306
@@ -305,8 +313,18 @@ static struct critnib_leaf *alloc_leaf(struct critnib *__restrict c) {
305313 }
306314
307315 struct critnib_leaf * k = c -> deleted_leaf ;
308-
309316 c -> deleted_leaf = k -> value ;
317+
318+ uint64_t ref_count = 0 ;
319+ void * to_be_freed = NULL ;
320+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
321+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
322+ if (ref_count && to_be_freed ) {
323+ LOG_DEBUG ("WARNING: reusing probably non-freed leaf: key = %p, value = "
324+ "%p, ref_count = %llu" ,
325+ (void * )k -> key , to_be_freed , (unsigned long long )ref_count );
326+ }
327+
310328 utils_annotate_memory_new (k , sizeof (* k ));
311329
312330 return k ;
@@ -334,8 +352,12 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
334352
335353 utils_annotate_memory_no_check (k , sizeof (struct critnib_leaf ));
336354
355+ utils_atomic_store_release_ptr (& k -> to_be_freed , 0 );
337356 utils_atomic_store_release_ptr ((void * * )& k -> key , (void * )key );
338- utils_atomic_store_release_ptr ((void * * )& k -> value , value );
357+ utils_atomic_store_release_ptr (& k -> value , value );
358+
359+ // mark the leaf as valid (ref_count == 1)
360+ utils_atomic_store_release_u64 (& k -> ref_count , 1ULL );
339361
340362 struct critnib_node * kn = (void * )((word )k | 1 );
341363
@@ -370,10 +392,6 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
370392 word at = path ^ key ;
371393 if (!at ) {
372394 ASSERT (is_leaf (n ));
373- if (to_leaf (kn )-> value == value ) {
374- // do not free the value
375- to_leaf (kn )-> value = NULL ;
376- }
377395 free_leaf (c , to_leaf (kn ));
378396
379397 if (update ) {
@@ -392,11 +410,10 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
392410 struct critnib_node * m = alloc_node (c );
393411 if (!m ) {
394412 free_leaf (c , to_leaf (kn ));
395-
396413 utils_mutex_unlock (& c -> mutex );
397-
398414 return ENOMEM ;
399415 }
416+
400417 utils_annotate_memory_no_check (m , sizeof (struct critnib_node ));
401418
402419 for (int i = 0 ; i < SLNODES ; i ++ ) {
@@ -418,19 +435,36 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
418435/*
419436 * critnib_remove -- delete a key from the critnib structure, return its value
420437 */
421- void * critnib_remove (struct critnib * c , word key ) {
438+ void * critnib_remove (struct critnib * c , word key , void * * ref ) {
422439 struct critnib_leaf * k ;
423440 void * value = NULL ;
424441
442+ if (!ref ) {
443+ return NULL ;
444+ }
445+
425446 utils_mutex_lock (& c -> mutex );
426447
427448 struct critnib_node * n = c -> root ;
428449 if (!n ) {
429450 goto not_found ;
430451 }
431452
432- word del =
433- (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
453+ word del ;
454+ int i_del = 0 ;
455+ uint64_t ref_count = 0 ;
456+ do {
457+ del = (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
458+ if (++ i_del == (DELETED_LIFE + 1 )) {
459+ break ;
460+ }
461+
462+ k = c -> pending_del_leaves [del ];
463+ if (k ) {
464+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
465+ }
466+ } while (k && (ref_count > 0 ));
467+
434468 free_node (c , c -> pending_del_nodes [del ]);
435469 free_leaf (c , c -> pending_del_leaves [del ]);
436470 c -> pending_del_nodes [del ] = NULL ;
@@ -491,13 +525,55 @@ void *critnib_remove(struct critnib *c, word key) {
491525
492526del_leaf :
493527 value = k -> value ;
528+ utils_atomic_store_release_ptr (& k -> to_be_freed , value );
529+ utils_atomic_store_release_ptr (& k -> value , NULL );
494530 c -> pending_del_leaves [del ] = k ;
531+ * ref = k ;
495532
496533not_found :
497534 utils_mutex_unlock (& c -> mutex );
498535 return value ;
499536}
500537
538+ /*
539+ * critnib_release -- release a reference to a key
540+ */
541+ int critnib_release (struct critnib * c , void * ref ) {
542+ if (!c || !ref ) {
543+ return -1 ;
544+ }
545+
546+ struct critnib_leaf * k = (struct critnib_leaf * )ref ;
547+
548+ uint64_t ref_count ;
549+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
550+
551+ if (ref_count == 0 ) {
552+ return -1 ;
553+ }
554+
555+ /* decrement the reference count */
556+ if (utils_atomic_decrement_u64 (& k -> ref_count ) == 0 ) {
557+ void * to_be_freed = NULL ;
558+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
559+ if (to_be_freed ) {
560+ utils_atomic_store_release_ptr (& k -> to_be_freed , NULL );
561+ if (c -> cb_free_leaf ) {
562+ c -> cb_free_leaf (c -> leaf_allocator , to_be_freed );
563+ }
564+ }
565+ }
566+
567+ #ifndef NDEBUG
568+ // check if the reference count is overflowed
569+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
570+ assert ((ref_count & (1ULL << 63 )) == 0 );
571+ assert (ref_count != (uint64_t )(0 - 1ULL ));
572+ #endif
573+
574+ return 0 ;
575+ }
576+
501577/*
502578 * critnib_get -- query for a key ("==" match), returns value or NULL
503579 *
@@ -508,13 +584,17 @@ void *critnib_remove(struct critnib *c, word key) {
508584 * Counterintuitively, it's pointless to return the most current answer,
509585 * we need only one that was valid at any point after the call started.
510586 */
511- void * critnib_get (struct critnib * c , word key ) {
587+ void * critnib_get (struct critnib * c , word key , void * * ref ) {
588+ struct critnib_leaf * k ;
589+ struct critnib_node * n ;
512590 uint64_t wrs1 , wrs2 ;
513- void * res ;
591+ void * res = NULL ;
514592
515- do {
516- struct critnib_node * n ;
593+ if (!ref ) {
594+ return NULL ;
595+ }
517596
597+ do {
518598 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs1 );
519599 utils_atomic_load_acquire_ptr ((void * * )& c -> root , (void * * )& n );
520600
@@ -524,16 +604,31 @@ void *critnib_get(struct critnib *c, word key) {
524604 * going wrong way if our path is missing, but that's ok...
525605 */
526606 while (n && !is_leaf (n )) {
607+ sh_t sh = n -> shift ;
527608 utils_atomic_load_acquire_ptr (
528- (void * * )& n -> child [slice_index (key , n -> shift )], (void * * )& n );
609+ (void * * )& n -> child [slice_index (key , sh )], (void * * )& n );
529610 }
530611
531612 /* ... as we check it at the end. */
532- struct critnib_leaf * k = to_leaf (n );
613+ k = to_leaf (n );
533614 res = (n && k -> key == key ) ? k -> value : NULL ;
534615 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
535616 } while (wrs1 + DELETED_LIFE <= wrs2 );
536617
618+ if (res ) {
619+ uint64_t ref_count ;
620+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
621+ if (ref_count == 0 ) {
622+ return NULL ;
623+ }
624+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
625+ utils_atomic_decrement_u64 (& k -> ref_count );
626+ return NULL ;
627+ }
628+
629+ * ref = k ;
630+ }
631+
537632 return res ;
538633}
539634
@@ -645,19 +740,42 @@ static struct critnib_leaf *find_le(struct critnib_node *__restrict n,
645740 *
646741 * Same guarantees as critnib_get().
647742 */
648- void * critnib_find_le (struct critnib * c , word key ) {
743+ void * critnib_find_le (struct critnib * c , word key , void * * ref ) {
744+ struct critnib_leaf * k ;
649745 uint64_t wrs1 , wrs2 ;
650746 void * res ;
651747
748+ if (!ref ) {
749+ return NULL ;
750+ }
751+
652752 do {
653753 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs1 );
654754 struct critnib_node * n ; /* avoid a subtle TOCTOU */
655755 utils_atomic_load_acquire_ptr ((void * * )& c -> root , (void * * )& n );
656- struct critnib_leaf * k = n ? find_le (n , key ) : NULL ;
657- res = k ? k -> value : NULL ;
756+ k = n ? find_le (n , key ) : NULL ;
757+ if (k ) {
758+ utils_atomic_load_acquire_ptr (& k -> value , & res );
759+ } else {
760+ res = NULL ;
761+ }
658762 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
659763 } while (wrs1 + DELETED_LIFE <= wrs2 );
660764
765+ if (res ) {
766+ uint64_t ref_count ;
767+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
768+ if (ref_count == 0 ) {
769+ return NULL ;
770+ }
771+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
772+ utils_atomic_decrement_u64 (& k -> ref_count );
773+ return NULL ;
774+ }
775+
776+ * ref = k ;
777+ }
778+
661779 return res ;
662780}
663781
@@ -743,12 +861,16 @@ static struct critnib_leaf *find_ge(struct critnib_node *__restrict n,
743861 * critnib_find -- parametrized query, returns 1 if found
744862 */
745863int critnib_find (struct critnib * c , uintptr_t key , enum find_dir_t dir ,
746- uintptr_t * rkey , void * * rvalue ) {
864+ uintptr_t * rkey , void * * rvalue , void * * ref ) {
747865 uint64_t wrs1 , wrs2 ;
748866 struct critnib_leaf * k ;
749867 uintptr_t _rkey = (uintptr_t )0x0 ;
750868 void * * _rvalue = NULL ;
751869
870+ if (!ref ) {
871+ return 0 ;
872+ }
873+
752874 /* <42 ≡ ≤41 */
753875 if (dir < -1 ) {
754876 if (!key ) {
@@ -790,6 +912,18 @@ int critnib_find(struct critnib *c, uintptr_t key, enum find_dir_t dir,
790912 } while (wrs1 + DELETED_LIFE <= wrs2 );
791913
792914 if (k ) {
915+ uint64_t ref_count ;
916+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
917+ if (ref_count == 0 ) {
918+ return 0 ;
919+ }
920+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
921+ utils_atomic_decrement_u64 (& k -> ref_count );
922+ return 0 ;
923+ }
924+
925+ * ref = k ;
926+
793927 if (rkey ) {
794928 * rkey = _rkey ;
795929 }
0 commit comments