Skip to content

Commit a2d9998

Browse files
authored
Merge pull request #1362 from ldorau/Fix_race_between_critnib_release_and_free_leaf_in_critnib
Fix race between critnib_release() and free_leaf() in critnib
2 parents b1ca59d + 02cc039 commit a2d9998

File tree

2 files changed

+85
-37
lines changed

2 files changed

+85
-37
lines changed

src/critnib/critnib.c

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
#define NIB ((1ULL << SLICE) - 1)
9090
#define SLNODES (1 << SLICE)
9191

92+
// it has to be >= 2
93+
#define LEAF_VALID (2ULL)
94+
9295
typedef uintptr_t word;
9396
typedef uint8_t sh_t;
9497

@@ -312,12 +315,13 @@ static void add_to_deleted_leaf_list(struct critnib *__restrict c,
312315
assert(k);
313316
struct critnib_leaf *deleted_leaf;
314317

315-
do {
316-
utils_atomic_load_acquire_ptr((void **)&c->deleted_leaf,
317-
(void **)&deleted_leaf);
318-
utils_atomic_store_release_ptr(&k->value, deleted_leaf);
319-
} while (!utils_compare_exchange_u64(
320-
(uint64_t *)&c->deleted_leaf, (uint64_t *)&k->value, (uint64_t *)&k));
318+
utils_atomic_load_acquire_ptr((void **)&c->deleted_leaf,
319+
(void **)&deleted_leaf);
320+
utils_atomic_store_release_ptr(&k->value, deleted_leaf);
321+
while (!utils_compare_exchange_u64((uint64_t *)&c->deleted_leaf,
322+
(uint64_t *)&k->value, (uint64_t *)&k)) {
323+
;
324+
}
321325
}
322326

323327
/*
@@ -331,18 +335,26 @@ static void free_leaf(struct critnib *__restrict c,
331335
return;
332336
}
333337

338+
// k should be added to the c->deleted_leaf list here
339+
// or in critnib_release() when the reference count drops to 0.
340+
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 1);
341+
334342
if (c->cb_free_leaf) {
335343
uint64_t ref_count;
336344
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
337345
if (ref_count > 0) {
338-
// k will be added to c->deleted_leaf in critnib_release()
346+
// k will be added to the c->deleted_leaf list in critnib_release()
339347
// when the reference count drops to 0.
340-
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 1);
341348
return;
342349
}
343350
}
344351

345-
add_to_deleted_leaf_list(c, k);
352+
uint8_t expected = 1;
353+
uint8_t desired = 0;
354+
if (utils_compare_exchange_u8(&k->pending_deleted_leaf, &expected,
355+
&desired)) {
356+
add_to_deleted_leaf_list(c, k);
357+
}
346358
}
347359

348360
/*
@@ -351,8 +363,8 @@ static void free_leaf(struct critnib *__restrict c,
351363
static struct critnib_leaf *alloc_leaf(struct critnib *__restrict c) {
352364
struct critnib_leaf *k;
353365

366+
utils_atomic_load_acquire_ptr((void **)&c->deleted_leaf, (void **)&k);
354367
do {
355-
utils_atomic_load_acquire_ptr((void **)&c->deleted_leaf, (void **)&k);
356368
if (!k) {
357369
return umf_ba_global_aligned_alloc(sizeof(struct critnib_leaf), 8);
358370
}
@@ -392,8 +404,8 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
392404
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 0);
393405

394406
if (c->cb_free_leaf) {
395-
// mark the leaf as valid (ref_count == 1)
396-
utils_atomic_store_release_u64(&k->ref_count, 1ULL);
407+
// mark the leaf as valid (ref_count == 2)
408+
utils_atomic_store_release_u64(&k->ref_count, LEAF_VALID);
397409
} else {
398410
// the reference counter is not used in this case
399411
utils_atomic_store_release_u64(&k->ref_count, 0ULL);
@@ -602,36 +614,52 @@ int critnib_release(struct critnib *c, void *ref) {
602614
struct critnib_leaf *k = (struct critnib_leaf *)ref;
603615

604616
uint64_t ref_count;
605-
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
606-
607-
if (ref_count == 0) {
608-
return -1;
609-
}
610-
617+
uint64_t ref_desired;
611618
/* decrement the reference count */
612-
if (utils_atomic_decrement_u64(&k->ref_count) == 0) {
613-
void *to_be_freed = NULL;
614-
utils_atomic_load_acquire_ptr(&k->to_be_freed, &to_be_freed);
615-
if (to_be_freed) {
616-
utils_atomic_store_release_ptr(&k->to_be_freed, NULL);
617-
c->cb_free_leaf(c->leaf_allocator, to_be_freed);
618-
}
619-
uint8_t pending_deleted_leaf;
620-
utils_atomic_load_acquire_u8(&k->pending_deleted_leaf,
621-
&pending_deleted_leaf);
622-
if (pending_deleted_leaf) {
623-
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 0);
624-
add_to_deleted_leaf_list(c, k);
619+
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
620+
do {
621+
if (ref_count < LEAF_VALID) {
622+
#ifndef NDEBUG
623+
LOG_FATAL("critnib_release() was called too many times (ref_count "
624+
"= %llu)\n",
625+
(unsigned long long)ref_count);
626+
assert(ref_count >= LEAF_VALID);
627+
#endif
628+
return -1;
625629
}
630+
ref_desired = ref_count - 1;
631+
} while (
632+
!utils_compare_exchange_u64(&k->ref_count, &ref_count, &ref_desired));
633+
634+
if (ref_desired >= LEAF_VALID) {
635+
// ref_counter was decremented and it is still valid
636+
return 0;
626637
}
627638

639+
/* ref_counter == (LEAF_VALID - 1)) - the leaf will be freed */
640+
void *to_be_freed = NULL;
641+
utils_atomic_load_acquire_ptr(&k->to_be_freed, &to_be_freed);
642+
utils_atomic_store_release_ptr(&k->to_be_freed, NULL);
628643
#ifndef NDEBUG
629-
// check if the reference count is overflowed
630-
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
631-
assert((ref_count & (1ULL << 63)) == 0);
632-
assert(ref_count != (uint64_t)(0 - 1ULL));
644+
if (to_be_freed == NULL) {
645+
LOG_FATAL("leaf will not be freed (to_be_freed == NULL, value = %p)\n",
646+
k->value);
647+
assert(to_be_freed != NULL);
648+
}
633649
#endif
634650

651+
// mark the leaf as not used (ref_count == 0)
652+
utils_atomic_store_release_u64(&k->ref_count, 0ULL);
653+
654+
c->cb_free_leaf(c->leaf_allocator, to_be_freed);
655+
656+
uint8_t expected = 1;
657+
uint8_t desired = 0;
658+
if (utils_compare_exchange_u8(&k->pending_deleted_leaf, &expected,
659+
&desired)) {
660+
add_to_deleted_leaf_list(c, k);
661+
}
662+
635663
return 0;
636664
}
637665

@@ -659,9 +687,9 @@ static inline int increment_ref_count(struct critnib_leaf *k) {
659687
uint64_t expected;
660688
uint64_t desired;
661689

690+
utils_atomic_load_acquire_u64(&k->ref_count, &expected);
662691
do {
663-
utils_atomic_load_acquire_u64(&k->ref_count, &expected);
664-
if (expected == 0) {
692+
if (expected < LEAF_VALID) {
665693
return -1;
666694
}
667695
desired = expected + 1;

src/utils/utils_concurrency.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,19 @@ static inline bool utils_compare_exchange_u64(uint64_t *ptr, uint64_t *expected,
171171
return false;
172172
}
173173

174+
static inline bool utils_compare_exchange_u8(uint8_t *ptr, uint8_t *expected,
175+
uint8_t *desired) {
176+
char out = _InterlockedCompareExchange8(
177+
(char volatile *)ptr, *(char *)desired, *(char *)expected);
178+
if (out == *(char *)expected) {
179+
return true;
180+
}
181+
182+
// else
183+
*expected = out;
184+
return false;
185+
}
186+
174187
#else // !defined(_WIN32)
175188

176189
static inline void utils_atomic_load_acquire_u64(uint64_t *ptr, uint64_t *out) {
@@ -241,6 +254,13 @@ static inline bool utils_compare_exchange_u64(uint64_t *ptr, uint64_t *expected,
241254
memory_order_relaxed);
242255
}
243256

257+
static inline bool utils_compare_exchange_u8(uint8_t *ptr, uint8_t *expected,
258+
uint8_t *desired) {
259+
return __atomic_compare_exchange(ptr, expected, desired, 0 /* strong */,
260+
memory_order_acq_rel,
261+
memory_order_relaxed);
262+
}
263+
244264
#endif // !defined(_WIN32)
245265

246266
static inline void utils_atomic_load_acquire_size_t(size_t *ptr, size_t *out) {

0 commit comments

Comments
 (0)