Skip to content

Commit 581ecd5

Browse files
committed
critnib: add reference counter
Signed-off-by: Lukasz Dorau <[email protected]>
1 parent 78a16e1 commit 581ecd5

File tree

6 files changed

+488
-112
lines changed

6 files changed

+488
-112
lines changed

src/critnib/critnib.c

Lines changed: 174 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

116118
struct critnib_leaf {
117119
word key;
118120
void *value;
121+
void *to_be_freed;
122+
uint64_t ref_count;
119123
};
120124

121125
struct critnib {
@@ -194,7 +198,12 @@ struct critnib *critnib_new(free_leaf_t cb_free_leaf, void *leaf_allocator) {
194198
static 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

492528
del_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

496535
not_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
*/
745871
int 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
}

src/critnib/critnib.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ critnib *critnib_new(free_leaf_t cb_free_leaf, void *leaf_allocator);
3232
void critnib_delete(critnib *c);
3333

3434
int critnib_insert(critnib *c, uintptr_t key, void *value, int update);
35-
void *critnib_remove(critnib *c, uintptr_t key);
36-
void *critnib_get(critnib *c, uintptr_t key);
37-
void *critnib_find_le(critnib *c, uintptr_t key);
35+
void *critnib_remove(critnib *c, uintptr_t key, void **ref);
36+
void *critnib_get(critnib *c, uintptr_t key, void **ref);
37+
void *critnib_find_le(critnib *c, uintptr_t key, void **ref);
3838
int critnib_find(critnib *c, uintptr_t key, enum find_dir_t dir,
39-
uintptr_t *rkey, void **rvalue);
39+
uintptr_t *rkey, void **rvalue, void **ref);
4040
void critnib_iter(critnib *c, uintptr_t min, uintptr_t max,
4141
int (*func)(uintptr_t key, void *value, void *privdata),
4242
void *privdata);
43+
int critnib_release(struct critnib *c, void *ref);
4344

4445
#ifdef __cplusplus
4546
}

0 commit comments

Comments
 (0)