@@ -111,11 +111,15 @@ struct critnib_node {
111111 struct critnib_node * child [SLNODES ];
112112 word path ;
113113 sh_t shift ;
114+ // padding to 8 bytes so that shift can be stored atomically as uint64_t
115+ sh_t unused [7 ];
114116};
115117
116118struct critnib_leaf {
117119 word key ;
118120 void * value ;
121+ void * to_be_freed ;
122+ uint64_t ref_count ;
119123};
120124
121125struct critnib {
@@ -194,7 +198,12 @@ struct critnib *critnib_new(free_leaf_t cb_free_leaf, void *leaf_allocator) {
194198static void delete_node (struct critnib * c , struct critnib_node * __restrict n ) {
195199 if (is_leaf (n )) {
196200 if (c -> cb_free_leaf && to_leaf (n )) {
197- c -> cb_free_leaf (c -> leaf_allocator , (void * )to_leaf (n )-> value );
201+ if (to_leaf (n )-> value ) {
202+ c -> cb_free_leaf (c -> leaf_allocator , (void * )to_leaf (n )-> value );
203+ } else if (to_leaf (n )-> to_be_freed ) {
204+ c -> cb_free_leaf (c -> leaf_allocator ,
205+ (void * )to_leaf (n )-> to_be_freed );
206+ }
198207 }
199208 umf_ba_global_free (to_leaf (n ));
200209 } else {
@@ -233,8 +242,13 @@ void critnib_delete(struct critnib *c) {
233242 for (int i = 0 ; i < DELETED_LIFE ; i ++ ) {
234243 umf_ba_global_free (c -> pending_del_nodes [i ]);
235244 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 );
245+ if (c -> pending_del_leaves [i ]-> value ) {
246+ c -> cb_free_leaf (c -> leaf_allocator ,
247+ (void * )c -> pending_del_leaves [i ]-> value );
248+ } else if (c -> pending_del_leaves [i ]-> to_be_freed ) {
249+ c -> cb_free_leaf (c -> leaf_allocator ,
250+ (void * )c -> pending_del_leaves [i ]-> to_be_freed );
251+ }
238252 }
239253 umf_ba_global_free (c -> pending_del_leaves [i ]);
240254 }
@@ -288,11 +302,7 @@ static void free_leaf(struct critnib *__restrict c,
288302 return ;
289303 }
290304
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 );
305+ utils_atomic_store_release_ptr (& k -> value , c -> deleted_leaf );
296306 utils_atomic_store_release_ptr ((void * * )& c -> deleted_leaf , k );
297307}
298308
@@ -305,8 +315,18 @@ static struct critnib_leaf *alloc_leaf(struct critnib *__restrict c) {
305315 }
306316
307317 struct critnib_leaf * k = c -> deleted_leaf ;
308-
309318 c -> deleted_leaf = k -> value ;
319+
320+ uint64_t ref_count = 0 ;
321+ void * to_be_freed = NULL ;
322+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
323+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
324+ if (ref_count && to_be_freed ) {
325+ LOG_DEBUG ("WARNING: reusing probably non-freed leaf: key = %p, value = "
326+ "%p, ref_count = %llu" ,
327+ (void * )k -> key , to_be_freed , (unsigned long long )ref_count );
328+ }
329+
310330 utils_annotate_memory_new (k , sizeof (* k ));
311331
312332 return k ;
@@ -334,8 +354,12 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
334354
335355 utils_annotate_memory_no_check (k , sizeof (struct critnib_leaf ));
336356
357+ utils_atomic_store_release_ptr (& k -> to_be_freed , 0 );
337358 utils_atomic_store_release_ptr ((void * * )& k -> key , (void * )key );
338- utils_atomic_store_release_ptr ((void * * )& k -> value , value );
359+ utils_atomic_store_release_ptr (& k -> value , value );
360+
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,10 +394,6 @@ 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 ) {
374- // do not free the value
375- to_leaf (kn )-> value = NULL ;
376- }
377397 free_leaf (c , to_leaf (kn ));
378398
379399 if (update ) {
@@ -392,11 +412,10 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
392412 struct critnib_node * m = alloc_node (c );
393413 if (!m ) {
394414 free_leaf (c , to_leaf (kn ));
395-
396415 utils_mutex_unlock (& c -> mutex );
397-
398416 return ENOMEM ;
399417 }
418+
400419 utils_annotate_memory_no_check (m , sizeof (struct critnib_node ));
401420
402421 for (int i = 0 ; i < SLNODES ; i ++ ) {
@@ -405,8 +424,8 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
405424
406425 utils_atomic_store_release_ptr ((void * )& m -> child [slice_index (key , sh )], kn );
407426 utils_atomic_store_release_ptr ((void * )& m -> child [slice_index (path , sh )], n );
408- m -> shift = sh ;
409- utils_atomic_store_release_u64 ((void * )& m -> path , key & path_mask (sh ));
427+ utils_atomic_store_release_u64 (( uint64_t * ) & m -> shift , ( uint64_t ) sh ) ;
428+ utils_atomic_store_release_u64 ((uint64_t * )& m -> path , key & path_mask (sh ));
410429
411430 utils_atomic_store_release_ptr ((void * * )parent , m );
412431
@@ -418,19 +437,36 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
418437/*
419438 * critnib_remove -- delete a key from the critnib structure, return its value
420439 */
421- void * critnib_remove (struct critnib * c , word key ) {
440+ void * critnib_remove (struct critnib * c , word key , void * * ref ) {
422441 struct critnib_leaf * k ;
423442 void * value = NULL ;
424443
444+ if (!ref ) {
445+ return NULL ;
446+ }
447+
425448 utils_mutex_lock (& c -> mutex );
426449
427450 struct critnib_node * n = c -> root ;
428451 if (!n ) {
429452 goto not_found ;
430453 }
431454
432- word del =
433- (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
455+ word del ;
456+ int i_del = 0 ;
457+ uint64_t ref_count = 0 ;
458+ do {
459+ del = (utils_atomic_increment_u64 (& c -> remove_count ) - 1 ) % DELETED_LIFE ;
460+ if (++ i_del == (DELETED_LIFE + 1 )) {
461+ break ;
462+ }
463+
464+ k = c -> pending_del_leaves [del ];
465+ if (k ) {
466+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
467+ }
468+ } while (k && (ref_count > 0 ));
469+
434470 free_node (c , c -> pending_del_nodes [del ]);
435471 free_leaf (c , c -> pending_del_leaves [del ]);
436472 c -> pending_del_nodes [del ] = NULL ;
@@ -491,13 +527,55 @@ void *critnib_remove(struct critnib *c, word key) {
491527
492528del_leaf :
493529 value = k -> value ;
530+ utils_atomic_store_release_ptr (& k -> to_be_freed , value );
531+ utils_atomic_store_release_ptr (& k -> value , NULL );
494532 c -> pending_del_leaves [del ] = k ;
533+ * ref = k ;
495534
496535not_found :
497536 utils_mutex_unlock (& c -> mutex );
498537 return value ;
499538}
500539
540+ /*
541+ * critnib_release -- release a reference to a key
542+ */
543+ int critnib_release (struct critnib * c , void * ref ) {
544+ if (!c || !ref ) {
545+ return -1 ;
546+ }
547+
548+ struct critnib_leaf * k = (struct critnib_leaf * )ref ;
549+
550+ uint64_t ref_count ;
551+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
552+
553+ if (ref_count == 0 ) {
554+ return -1 ;
555+ }
556+
557+ /* decrement the reference count */
558+ if (utils_atomic_decrement_u64 (& k -> ref_count ) == 0 ) {
559+ void * to_be_freed = NULL ;
560+ utils_atomic_load_acquire_ptr (& k -> to_be_freed , & to_be_freed );
561+ if (to_be_freed ) {
562+ utils_atomic_store_release_ptr (& k -> to_be_freed , NULL );
563+ if (c -> cb_free_leaf ) {
564+ c -> cb_free_leaf (c -> leaf_allocator , to_be_freed );
565+ }
566+ }
567+ }
568+
569+ #ifndef NDEBUG
570+ // check if the reference count is overflowed
571+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
572+ assert ((ref_count & (1ULL << 63 )) == 0 );
573+ assert (ref_count != (uint64_t )(0 - 1ULL ));
574+ #endif
575+
576+ return 0 ;
577+ }
578+
501579/*
502580 * critnib_get -- query for a key ("==" match), returns value or NULL
503581 *
@@ -508,13 +586,19 @@ void *critnib_remove(struct critnib *c, word key) {
508586 * Counterintuitively, it's pointless to return the most current answer,
509587 * we need only one that was valid at any point after the call started.
510588 */
511- void * critnib_get (struct critnib * c , word key ) {
589+ void * critnib_get (struct critnib * c , word key , void * * ref ) {
590+ struct critnib_leaf * k ;
591+ struct critnib_node * n ;
512592 uint64_t wrs1 , wrs2 ;
513- void * res ;
593+ void * res = NULL ;
594+ uint64_t shift64 ;
595+ sh_t shift ;
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
@@ -524,16 +608,32 @@ void *critnib_get(struct critnib *c, word key) {
524608 * going wrong way if our path is missing, but that's ok...
525609 */
526610 while (n && !is_leaf (n )) {
611+ utils_atomic_load_acquire_u64 ((uint64_t * )& n -> shift , & shift64 );
612+ shift = (sh_t )shift64 ;
527613 utils_atomic_load_acquire_ptr (
528- (void * * )& n -> child [slice_index (key , n -> shift )], (void * * )& n );
614+ (void * * )& n -> child [slice_index (key , shift )], (void * * )& n );
529615 }
530616
531617 /* ... as we check it at the end. */
532- struct critnib_leaf * k = to_leaf (n );
618+ k = to_leaf (n );
533619 res = (n && k -> key == key ) ? k -> value : NULL ;
534620 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
535621 } while (wrs1 + DELETED_LIFE <= wrs2 );
536622
623+ if (res ) {
624+ uint64_t ref_count ;
625+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
626+ if (ref_count == 0 ) {
627+ return NULL ;
628+ }
629+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
630+ utils_atomic_decrement_u64 (& k -> ref_count );
631+ return NULL ;
632+ }
633+
634+ * ref = k ;
635+ }
636+
537637 return res ;
538638}
539639
@@ -590,7 +690,10 @@ static struct critnib_leaf *find_le(struct critnib_node *__restrict n,
590690 * needs to be masked away as well.
591691 */
592692 word path ;
593- sh_t shift = n -> shift ;
693+ uint64_t shift64 ;
694+ sh_t shift ;
695+ utils_atomic_load_acquire_u64 ((uint64_t * )& n -> shift , & shift64 );
696+ shift = (sh_t )shift64 ;
594697 utils_atomic_load_acquire_u64 ((uint64_t * )& n -> path , (uint64_t * )& path );
595698 if ((key ^ path ) >> (shift ) & ~NIB ) {
596699 /*
@@ -645,19 +748,42 @@ static struct critnib_leaf *find_le(struct critnib_node *__restrict n,
645748 *
646749 * Same guarantees as critnib_get().
647750 */
648- void * critnib_find_le (struct critnib * c , word key ) {
751+ void * critnib_find_le (struct critnib * c , word key , void * * ref ) {
752+ struct critnib_leaf * k ;
649753 uint64_t wrs1 , wrs2 ;
650754 void * res ;
651755
756+ if (!ref ) {
757+ return NULL ;
758+ }
759+
652760 do {
653761 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs1 );
654762 struct critnib_node * n ; /* avoid a subtle TOCTOU */
655763 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 ;
764+ k = n ? find_le (n , key ) : NULL ;
765+ if (k ) {
766+ utils_atomic_load_acquire_ptr (& k -> value , & res );
767+ } else {
768+ res = NULL ;
769+ }
658770 utils_atomic_load_acquire_u64 (& c -> remove_count , & wrs2 );
659771 } while (wrs1 + DELETED_LIFE <= wrs2 );
660772
773+ if (res ) {
774+ uint64_t ref_count ;
775+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
776+ if (ref_count == 0 ) {
777+ return NULL ;
778+ }
779+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
780+ utils_atomic_decrement_u64 (& k -> ref_count );
781+ return NULL ;
782+ }
783+
784+ * ref = k ;
785+ }
786+
661787 return res ;
662788}
663789
@@ -743,12 +869,16 @@ static struct critnib_leaf *find_ge(struct critnib_node *__restrict n,
743869 * critnib_find -- parametrized query, returns 1 if found
744870 */
745871int critnib_find (struct critnib * c , uintptr_t key , enum find_dir_t dir ,
746- uintptr_t * rkey , void * * rvalue ) {
872+ uintptr_t * rkey , void * * rvalue , void * * ref ) {
747873 uint64_t wrs1 , wrs2 ;
748874 struct critnib_leaf * k ;
749875 uintptr_t _rkey = (uintptr_t )0x0 ;
750876 void * * _rvalue = NULL ;
751877
878+ if (!ref ) {
879+ return 0 ;
880+ }
881+
752882 /* <42 ≡ ≤41 */
753883 if (dir < -1 ) {
754884 if (!key ) {
@@ -790,6 +920,18 @@ int critnib_find(struct critnib *c, uintptr_t key, enum find_dir_t dir,
790920 } while (wrs1 + DELETED_LIFE <= wrs2 );
791921
792922 if (k ) {
923+ uint64_t ref_count ;
924+ utils_atomic_load_acquire_u64 (& k -> ref_count , & ref_count );
925+ if (ref_count == 0 ) {
926+ return 0 ;
927+ }
928+ if (utils_atomic_increment_u64 (& k -> ref_count ) == 1 ) {
929+ utils_atomic_decrement_u64 (& k -> ref_count );
930+ return 0 ;
931+ }
932+
933+ * ref = k ;
934+
793935 if (rkey ) {
794936 * rkey = _rkey ;
795937 }
0 commit comments