Skip to content

Commit 56db3fd

Browse files
committed
safe iterator tracking in hashtable. prevents invalid access if hashtable deleted from under safe iterator
Signed-off-by: Rain Valentine <[email protected]>
1 parent c88c94e commit 56db3fd

File tree

5 files changed

+318
-6
lines changed

5 files changed

+318
-6
lines changed

src/hashtable.c

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ typedef struct hashtableBucket {
284284
/* A key property is that the bucket size is one cache line. */
285285
static_assert(sizeof(bucket) == HASHTABLE_BUCKET_SIZE, "Bucket size mismatch");
286286

287+
/* Forward declaration for iter type */
288+
typedef struct iter iter;
289+
287290
struct hashtable {
288291
hashtableType *type;
289292
ssize_t rehash_idx; /* -1 = rehashing not in progress. */
@@ -293,10 +296,11 @@ struct hashtable {
293296
int16_t pause_rehash; /* Non-zero = rehashing is paused */
294297
int16_t pause_auto_shrink; /* Non-zero = automatic resizing disallowed. */
295298
size_t child_buckets[2]; /* Number of allocated child buckets. */
299+
iter *safe_iterators; /* Head of linked list of safe iterators */
296300
void *metadata[];
297301
};
298302

299-
typedef struct {
303+
struct iter {
300304
hashtable *hashtable;
301305
bucket *bucket;
302306
long index;
@@ -309,7 +313,8 @@ typedef struct {
309313
/* Safe iterator temporary storage for bucket chain compaction. */
310314
uint64_t last_seen_size;
311315
};
312-
} iter;
316+
iter *next_safe_iter; /* Next safe iterator in hashtable's list */
317+
};
313318

314319
/* The opaque hashtableIterator is defined as a blob of bytes. */
315320
static_assert(sizeof(hashtableIterator) >= sizeof(iter),
@@ -1084,6 +1089,41 @@ static inline int shouldPrefetchValues(iter *iter) {
10841089
return (iter->flags & HASHTABLE_ITER_PREFETCH_VALUES);
10851090
}
10861091

1092+
/* Add a safe iterator to the hashtable's tracking list */
1093+
static void trackSafeIterator(iter *it) {
1094+
hashtable *ht = it->hashtable;
1095+
it->next_safe_iter = ht->safe_iterators;
1096+
ht->safe_iterators = it;
1097+
}
1098+
1099+
/* Remove a safe iterator from the hashtable's tracking list */
1100+
static void untrackSafeIterator(iter *it) {
1101+
hashtable *ht = it->hashtable;
1102+
if (ht->safe_iterators == it) {
1103+
ht->safe_iterators = it->next_safe_iter;
1104+
} else {
1105+
iter *prev = ht->safe_iterators;
1106+
while (prev && prev->next_safe_iter != it) {
1107+
prev = prev->next_safe_iter;
1108+
}
1109+
if (prev) {
1110+
prev->next_safe_iter = it->next_safe_iter;
1111+
}
1112+
}
1113+
it->next_safe_iter = NULL;
1114+
it->hashtable = NULL;
1115+
}
1116+
1117+
/* Invalidate all safe iterators by setting hashtable = NULL */
1118+
static void invalidateAllSafeIterators(hashtable *ht) {
1119+
iter *it = ht->safe_iterators;
1120+
while (it) {
1121+
it->hashtable = NULL; /* Mark as invalid */
1122+
it = it->next_safe_iter;
1123+
}
1124+
ht->safe_iterators = NULL;
1125+
}
1126+
10871127
/* --- API functions --- */
10881128

10891129
/* Allocates and initializes a new hashtable specified by the given type. */
@@ -1098,6 +1138,7 @@ hashtable *hashtableCreate(hashtableType *type) {
10981138
ht->rehash_idx = -1;
10991139
ht->pause_rehash = 0;
11001140
ht->pause_auto_shrink = 0;
1141+
ht->safe_iterators = NULL;
11011142
resetTable(ht, 0);
11021143
resetTable(ht, 1);
11031144
if (type->trackMemUsage) type->trackMemUsage(ht, alloc_size);
@@ -1153,6 +1194,7 @@ void hashtableEmpty(hashtable *ht, void(callback)(hashtable *)) {
11531194

11541195
/* Deletes all the entries and frees the table. */
11551196
void hashtableRelease(hashtable *ht) {
1197+
invalidateAllSafeIterators(ht);
11561198
hashtableEmpty(ht, NULL);
11571199
/* Call trackMemUsage before zfree, so trackMemUsage can access ht. */
11581200
if (ht->type->trackMemUsage) {
@@ -1962,7 +2004,9 @@ size_t hashtableScanDefrag(hashtable *ht, size_t cursor, hashtableScanFunction f
19622004
* rehashing to prevent entries from moving around. It's allowed to insert and
19632005
* replace entries. Deleting entries is only allowed for the entry that was just
19642006
* returned by hashtableNext. Deleting other entries is possible, but doing so
1965-
* can cause internal fragmentation, so don't.
2007+
* can cause internal fragmentation, so don't. The hash table itself can be
2008+
* safely deleted while safe iterators exist - they will be invalidated and
2009+
* subsequent calls to hashtableNext will return false.
19662010
*
19672011
* Guarantees for safe iterators:
19682012
*
@@ -1988,6 +2032,10 @@ void hashtableInitIterator(hashtableIterator *iterator, hashtable *ht, uint8_t f
19882032
iter->table = 0;
19892033
iter->index = -1;
19902034
iter->flags = flags;
2035+
iter->next_safe_iter = NULL;
2036+
if (isSafe(iter) && ht != NULL) {
2037+
trackSafeIterator(iter);
2038+
}
19912039
}
19922040

19932041
/* Reinitializes the iterator for the provided hashtable while
@@ -2000,10 +2048,13 @@ void hashtableReinitIterator(hashtableIterator *iterator, hashtable *ht) {
20002048
/* Resets a stack-allocated iterator. */
20012049
void hashtableResetIterator(hashtableIterator *iterator) {
20022050
iter *iter = iteratorFromOpaque(iterator);
2051+
if (iter->hashtable == NULL) return;
2052+
20032053
if (!(iter->index == -1 && iter->table == 0)) {
20042054
if (isSafe(iter)) {
20052055
hashtableResumeRehashing(iter->hashtable);
20062056
assert(iter->hashtable->pause_rehash >= 0);
2057+
untrackSafeIterator(iter);
20072058
} else {
20082059
assert(iter->fingerprint == hashtableFingerprint(iter->hashtable));
20092060
}
@@ -2030,6 +2081,11 @@ void hashtableReleaseIterator(hashtableIterator *iterator) {
20302081
* Returns false if there are no more entries. */
20312082
bool hashtableNext(hashtableIterator *iterator, void **elemptr) {
20322083
iter *iter = iteratorFromOpaque(iterator);
2084+
/* Check if iterator has been invalidated */
2085+
if (iter->hashtable == NULL) {
2086+
return false;
2087+
}
2088+
20332089
while (1) {
20342090
if (iter->index == -1 && iter->table == 0) {
20352091
/* It's the first call to next. */

src/hashtable.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ typedef struct hashtable hashtable;
3939
typedef struct hashtableStats hashtableStats;
4040

4141
/* Can types that can be stack allocated. */
42-
typedef uint64_t hashtableIterator[5];
42+
typedef uint64_t hashtableIterator[6];
4343
typedef uint64_t hashtablePosition[2];
4444
typedef uint64_t hashtableIncrementalFindState[5];
4545

src/unit/test_files.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ int test_compact_bucket_chain(int argc, char **argv, int flags);
4141
int test_random_entry(int argc, char **argv, int flags);
4242
int test_random_entry_with_long_chain(int argc, char **argv, int flags);
4343
int test_random_entry_sparse_table(int argc, char **argv, int flags);
44+
int test_safe_iterator_invalidation(int argc, char **argv, int flags);
45+
int test_safe_iterator_empty_no_invalidation(int argc, char **argv, int flags);
46+
int test_safe_iterator_reset_invalidation(int argc, char **argv, int flags);
47+
int test_safe_iterator_reset_untracking(int argc, char **argv, int flags);
48+
int test_safe_iterator_pause_resume_tracking(int argc, char **argv, int flags);
4449
int test_intsetValueEncodings(int argc, char **argv, int flags);
4550
int test_intsetBasicAdding(int argc, char **argv, int flags);
4651
int test_intsetLargeNumberRandomAdd(int argc, char **argv, int flags);
@@ -261,7 +266,7 @@ unitTest __test_crc64combine_c[] = {{"test_crc64combine", test_crc64combine}, {N
261266
unitTest __test_dict_c[] = {{"test_dictCreate", test_dictCreate}, {"test_dictAdd16Keys", test_dictAdd16Keys}, {"test_dictDisableResize", test_dictDisableResize}, {"test_dictAddOneKeyTriggerResize", test_dictAddOneKeyTriggerResize}, {"test_dictDeleteKeys", test_dictDeleteKeys}, {"test_dictDeleteOneKeyTriggerResize", test_dictDeleteOneKeyTriggerResize}, {"test_dictEmptyDirAdd128Keys", test_dictEmptyDirAdd128Keys}, {"test_dictDisableResizeReduceTo3", test_dictDisableResizeReduceTo3}, {"test_dictDeleteOneKeyTriggerResizeAgain", test_dictDeleteOneKeyTriggerResizeAgain}, {"test_dictBenchmark", test_dictBenchmark}, {NULL, NULL}};
262267
unitTest __test_endianconv_c[] = {{"test_endianconv", test_endianconv}, {NULL, NULL}};
263268
unitTest __test_entry_c[] = {{"test_entryCreate", test_entryCreate}, {"test_entryUpdate", test_entryUpdate}, {"test_entryHasexpiry_entrySetExpiry", test_entryHasexpiry_entrySetExpiry}, {"test_entryIsExpired", test_entryIsExpired}, {"test_entryMemUsage_entrySetExpiry_entrySetValue", test_entryMemUsage_entrySetExpiry_entrySetValue}, {NULL, NULL}};
264-
unitTest __test_hashtable_c[] = {{"test_cursor", test_cursor}, {"test_set_hash_function_seed", test_set_hash_function_seed}, {"test_add_find_delete", test_add_find_delete}, {"test_add_find_delete_avoid_resize", test_add_find_delete_avoid_resize}, {"test_instant_rehashing", test_instant_rehashing}, {"test_bucket_chain_length", test_bucket_chain_length}, {"test_two_phase_insert_and_pop", test_two_phase_insert_and_pop}, {"test_replace_reallocated_entry", test_replace_reallocated_entry}, {"test_incremental_find", test_incremental_find}, {"test_scan", test_scan}, {"test_iterator", test_iterator}, {"test_safe_iterator", test_safe_iterator}, {"test_compact_bucket_chain", test_compact_bucket_chain}, {"test_random_entry", test_random_entry}, {"test_random_entry_with_long_chain", test_random_entry_with_long_chain}, {"test_random_entry_sparse_table", test_random_entry_sparse_table}, {NULL, NULL}};
269+
unitTest __test_hashtable_c[] = {{"test_cursor", test_cursor}, {"test_set_hash_function_seed", test_set_hash_function_seed}, {"test_add_find_delete", test_add_find_delete}, {"test_add_find_delete_avoid_resize", test_add_find_delete_avoid_resize}, {"test_instant_rehashing", test_instant_rehashing}, {"test_bucket_chain_length", test_bucket_chain_length}, {"test_two_phase_insert_and_pop", test_two_phase_insert_and_pop}, {"test_replace_reallocated_entry", test_replace_reallocated_entry}, {"test_incremental_find", test_incremental_find}, {"test_scan", test_scan}, {"test_iterator", test_iterator}, {"test_safe_iterator", test_safe_iterator}, {"test_compact_bucket_chain", test_compact_bucket_chain}, {"test_random_entry", test_random_entry}, {"test_random_entry_with_long_chain", test_random_entry_with_long_chain}, {"test_random_entry_sparse_table", test_random_entry_sparse_table}, {"test_safe_iterator_invalidation", test_safe_iterator_invalidation}, {"test_safe_iterator_empty_no_invalidation", test_safe_iterator_empty_no_invalidation}, {"test_safe_iterator_reset_invalidation", test_safe_iterator_reset_invalidation}, {"test_safe_iterator_reset_untracking", test_safe_iterator_reset_untracking}, {"test_safe_iterator_pause_resume_tracking", test_safe_iterator_pause_resume_tracking}, {NULL, NULL}};
265270
unitTest __test_intset_c[] = {{"test_intsetValueEncodings", test_intsetValueEncodings}, {"test_intsetBasicAdding", test_intsetBasicAdding}, {"test_intsetLargeNumberRandomAdd", test_intsetLargeNumberRandomAdd}, {"test_intsetUpgradeFromint16Toint32", test_intsetUpgradeFromint16Toint32}, {"test_intsetUpgradeFromint16Toint64", test_intsetUpgradeFromint16Toint64}, {"test_intsetUpgradeFromint32Toint64", test_intsetUpgradeFromint32Toint64}, {"test_intsetStressLookups", test_intsetStressLookups}, {"test_intsetStressAddDelete", test_intsetStressAddDelete}, {NULL, NULL}};
266271
unitTest __test_kvstore_c[] = {{"test_kvstoreAdd16Keys", test_kvstoreAdd16Keys}, {"test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyHashtable", test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyHashtable}, {"test_kvstoreIteratorRemoveAllKeysDeleteEmptyHashtable", test_kvstoreIteratorRemoveAllKeysDeleteEmptyHashtable}, {"test_kvstoreHashtableIteratorRemoveAllKeysNoDeleteEmptyHashtable", test_kvstoreHashtableIteratorRemoveAllKeysNoDeleteEmptyHashtable}, {"test_kvstoreHashtableIteratorRemoveAllKeysDeleteEmptyHashtable", test_kvstoreHashtableIteratorRemoveAllKeysDeleteEmptyHashtable}, {"test_kvstoreHashtableExpand", test_kvstoreHashtableExpand}, {NULL, NULL}};
267272
unitTest __test_listpack_c[] = {{"test_listpackCreateIntList", test_listpackCreateIntList}, {"test_listpackCreateList", test_listpackCreateList}, {"test_listpackLpPrepend", test_listpackLpPrepend}, {"test_listpackLpPrependInteger", test_listpackLpPrependInteger}, {"test_listpackGetELementAtIndex", test_listpackGetELementAtIndex}, {"test_listpackPop", test_listpackPop}, {"test_listpackGetELementAtIndex2", test_listpackGetELementAtIndex2}, {"test_listpackIterate0toEnd", test_listpackIterate0toEnd}, {"test_listpackIterate1toEnd", test_listpackIterate1toEnd}, {"test_listpackIterate2toEnd", test_listpackIterate2toEnd}, {"test_listpackIterateBackToFront", test_listpackIterateBackToFront}, {"test_listpackIterateBackToFrontWithDelete", test_listpackIterateBackToFrontWithDelete}, {"test_listpackDeleteWhenNumIsMinusOne", test_listpackDeleteWhenNumIsMinusOne}, {"test_listpackDeleteWithNegativeIndex", test_listpackDeleteWithNegativeIndex}, {"test_listpackDeleteInclusiveRange0_0", test_listpackDeleteInclusiveRange0_0}, {"test_listpackDeleteInclusiveRange0_1", test_listpackDeleteInclusiveRange0_1}, {"test_listpackDeleteInclusiveRange1_2", test_listpackDeleteInclusiveRange1_2}, {"test_listpackDeleteWitStartIndexOutOfRange", test_listpackDeleteWitStartIndexOutOfRange}, {"test_listpackDeleteWitNumOverflow", test_listpackDeleteWitNumOverflow}, {"test_listpackBatchDelete", test_listpackBatchDelete}, {"test_listpackDeleteFooWhileIterating", test_listpackDeleteFooWhileIterating}, {"test_listpackReplaceWithSameSize", test_listpackReplaceWithSameSize}, {"test_listpackReplaceWithDifferentSize", test_listpackReplaceWithDifferentSize}, {"test_listpackRegressionGt255Bytes", test_listpackRegressionGt255Bytes}, {"test_listpackCreateLongListAndCheckIndices", test_listpackCreateLongListAndCheckIndices}, {"test_listpackCompareStrsWithLpEntries", test_listpackCompareStrsWithLpEntries}, {"test_listpackLpMergeEmptyLps", test_listpackLpMergeEmptyLps}, {"test_listpackLpMergeLp1Larger", test_listpackLpMergeLp1Larger}, {"test_listpackLpMergeLp2Larger", test_listpackLpMergeLp2Larger}, {"test_listpackLpNextRandom", test_listpackLpNextRandom}, {"test_listpackLpNextRandomCC", test_listpackLpNextRandomCC}, {"test_listpackRandomPairWithOneElement", test_listpackRandomPairWithOneElement}, {"test_listpackRandomPairWithManyElements", test_listpackRandomPairWithManyElements}, {"test_listpackRandomPairsWithOneElement", test_listpackRandomPairsWithOneElement}, {"test_listpackRandomPairsWithManyElements", test_listpackRandomPairsWithManyElements}, {"test_listpackRandomPairsUniqueWithOneElement", test_listpackRandomPairsUniqueWithOneElement}, {"test_listpackRandomPairsUniqueWithManyElements", test_listpackRandomPairsUniqueWithManyElements}, {"test_listpackPushVariousEncodings", test_listpackPushVariousEncodings}, {"test_listpackLpFind", test_listpackLpFind}, {"test_listpackLpValidateIntegrity", test_listpackLpValidateIntegrity}, {"test_listpackNumberOfElementsExceedsLP_HDR_NUMELE_UNKNOWN", test_listpackNumberOfElementsExceedsLP_HDR_NUMELE_UNKNOWN}, {"test_listpackStressWithRandom", test_listpackStressWithRandom}, {"test_listpackSTressWithVariableSize", test_listpackSTressWithVariableSize}, {"test_listpackBenchmarkInit", test_listpackBenchmarkInit}, {"test_listpackBenchmarkLpAppend", test_listpackBenchmarkLpAppend}, {"test_listpackBenchmarkLpFindString", test_listpackBenchmarkLpFindString}, {"test_listpackBenchmarkLpFindNumber", test_listpackBenchmarkLpFindNumber}, {"test_listpackBenchmarkLpSeek", test_listpackBenchmarkLpSeek}, {"test_listpackBenchmarkLpValidateIntegrity", test_listpackBenchmarkLpValidateIntegrity}, {"test_listpackBenchmarkLpCompareWithString", test_listpackBenchmarkLpCompareWithString}, {"test_listpackBenchmarkLpCompareWithNumber", test_listpackBenchmarkLpCompareWithNumber}, {"test_listpackBenchmarkFree", test_listpackBenchmarkFree}, {NULL, NULL}};

0 commit comments

Comments
 (0)