Skip to content

Commit a7ccb3e

Browse files
authored
cmap: Addressing data race detected by TSAN (#22)
This commit resolves a data race issue identified by ThreadSanitizer. Most of the data race instances are rectified by implementing atomic operations to synchronize counter and status variables. While several fixes are straightforward, some require careful consideration due to potential deviations from the original behavior. The primary data race scenario pertains to the manipulation of the random seed. To enhance thread independence, a thread-local storage variable is now utilized to manage individual thread seeds effectively. Another complex challenge involves preventing a race condition between the writer thread, responsible for removal, and other reader threads. This situation poses a risk of freeing memory that readers are still using, resulting in use-after-free bugs. In pursuit of a practical yet efficient solution, a basic approach has been adopted: maintaining a linked list of pending memory releases. This memory is retained until the program's termination. Although not the most sophisticated method, it ensures correctness within the context of this project's aim to provide a user-friendly example. These changes collectively enhance the stability and reliability of the codebase while addressing the reported data race concerns.
1 parent b0b56b0 commit a7ccb3e

File tree

6 files changed

+47
-20
lines changed

6 files changed

+47
-20
lines changed

cmap/Makefile

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
CFLAGS = -Wall -O2
2+
3+
ifeq ("$(TSAN)", "1")
4+
CFLAGS += -g -fsanitize=thread
5+
endif
6+
17
all:
2-
gcc -Wall -O2 -o test-cmap \
8+
gcc $(CFLAGS) -o test-cmap \
39
cmap.c random.c rcu.c test-cmap.c \
410
-lpthread
511

cmap/cmap.c

+6-5
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ static void cmap_insert__(struct cmap_impl *impl, struct cmap_node *node)
3535
size_t i = node->hash & impl->max;
3636
node->next = impl->arr[i].first;
3737
if (!impl->arr[i].first)
38-
impl->utilization++;
39-
impl->arr[i].first = node;
38+
atomic_fetch_add(&impl->utilization, 1);
39+
atomic_store(&impl->arr[i].first, node);
4040
}
4141

4242
static void cmap_destroy_callback(void *args)
@@ -142,7 +142,7 @@ double cmap_utilization(const struct cmap *cmap)
142142
{
143143
struct rcu *impl_rcu = rcu_acquire(cmap->impl->p);
144144
struct cmap_impl *impl = rcu_get(impl_rcu, struct cmap_impl *);
145-
double res = (double) impl->utilization / (impl->max + 1);
145+
double res = (double) atomic_load(&impl->utilization) / (impl->max + 1);
146146
rcu_release(impl_rcu);
147147
return res;
148148
}
@@ -182,7 +182,7 @@ size_t cmap_remove(struct cmap *cmap, struct cmap_node *node)
182182
struct cmap_node **node_p = &cmap_entry->first;
183183
while (*node_p) {
184184
if (*node_p == node) {
185-
*node_p = node->next;
185+
atomic_store(node_p, node->next);
186186
count--;
187187
break;
188188
}
@@ -214,12 +214,13 @@ struct cmap_cursor cmap_find__(struct cmap_state state, uint32_t hash)
214214

215215
struct cmap_cursor cursor = {
216216
.entry_idx = hash & impl->max,
217-
.node = impl->arr[hash & impl->max].first,
217+
.node = atomic_load(&impl->arr[hash & impl->max].first),
218218
.next = NULL,
219219
.accross_entries = false,
220220
};
221221
if (cursor.node)
222222
cursor.next = cursor.node->next;
223+
223224
return cursor;
224225
}
225226

cmap/locks.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ static inline void mutex_lock_at(struct mutex *mutex, const char *where)
102102
return;
103103
if (pthread_mutex_lock(&mutex->lock))
104104
abort_msg("pthread_mutex_lock fail");
105-
mutex->where = where;
105+
atomic_store_explicit(&mutex->where, where, memory_order_relaxed);
106106
}
107107

108108
static inline void mutex_unlock(struct mutex *mutex)
@@ -111,7 +111,7 @@ static inline void mutex_unlock(struct mutex *mutex)
111111
return;
112112
if (pthread_mutex_unlock(&mutex->lock))
113113
abort_msg("pthread_mutex_unlock fail");
114-
mutex->where = NULL;
114+
atomic_store_explicit(&mutex->where, NULL, memory_order_relaxed);
115115
}
116116

117117
static inline void cond_init(struct cond *cond)

cmap/random.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
#include "random.h"
77
#include "util.h"
88

9-
static uint32_t seed = 0;
9+
/* Maintain thread-independent random seed to prevent race. */
10+
static __thread uint32_t seed = 0;
1011

1112
static uint32_t random_next(void);
1213

cmap/test-cmap

88 KB
Binary file not shown.

cmap/test-cmap.c

+30-11
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
struct elem {
1818
struct cmap_node node;
1919
uint32_t value;
20+
struct elem *next;
2021
};
2122

23+
static struct elem *freelist = NULL;
2224
static size_t num_values;
2325
static uint32_t max_value;
2426
static uint32_t *values;
@@ -67,6 +69,13 @@ static void destroy_values()
6769
struct elem *elem;
6870
free(values);
6971

72+
elem = freelist;
73+
while (elem) {
74+
struct elem *next = elem->next;
75+
free(elem);
76+
elem = next;
77+
}
78+
7079
cmap_state = cmap_state_acquire(&cmap_values);
7180
MAP_FOREACH (elem, node, cmap_state) {
7281
cmap_remove(&cmap_values, &elem->node);
@@ -107,11 +116,11 @@ static void *update_cmap(void *args)
107116
struct elem *elem;
108117
struct cmap_state cmap_state;
109118

110-
while (running) {
119+
while (atomic_load_explicit(&running, memory_order_relaxed)) {
111120
/* Insert */
112121
uint32_t value = random_uint32() + max_value + 1;
113122
insert_value(value);
114-
inserts++;
123+
atomic_fetch_add_explicit(&inserts, 1, memory_order_relaxed);
115124
wait();
116125

117126
/* Remove */
@@ -120,8 +129,16 @@ static void *update_cmap(void *args)
120129
MAP_FOREACH_WITH_HASH (elem, node, hash, cmap_state) {
121130
if (elem->value > max_value) {
122131
cmap_remove(&cmap_values, &elem->node);
123-
free(elem);
124-
removes++;
132+
/* FIXME: If we free 'elem' directly, it may lead to use-after-free
133+
* when the reader thread obtains it. To deal with the issue, here
134+
* we just simply keep it in a list and release the memory at the end
135+
* of program.
136+
*
137+
* We should consider better strategy like reference counting or hazard pointer,
138+
* which allow us to free each chunk of memory at the correct time */
139+
elem->next = freelist;
140+
freelist = elem;
141+
atomic_fetch_add_explicit(&removes, 1, memory_order_relaxed);
125142
break;
126143
}
127144
}
@@ -134,18 +151,18 @@ static void *update_cmap(void *args)
134151
/* Constantly check whever values in cmap can be composed */
135152
static void *read_cmap(void *args)
136153
{
137-
while (running) {
154+
while (atomic_load_explicit(&running, memory_order_relaxed)) {
138155
uint32_t index = random_uint32() % num_values;
139156
if (!can_compose_value(values[index])) {
140-
running = false;
141-
error = true;
157+
atomic_store_explicit(&running, false, memory_order_relaxed);
158+
atomic_store_explicit(&error, true, memory_order_relaxed);
142159
break;
143160
}
144161
atomic_fetch_add(&checks, 1);
145162
wait();
146163
if (can_compose_value(values[index] + max_value + 1)) {
147-
running = false;
148-
error = true;
164+
atomic_store_explicit(&running, false, memory_order_relaxed);
165+
atomic_store_explicit(&error, true, memory_order_relaxed);
149166
break;
150167
}
151168
atomic_fetch_add(&checks, 1);
@@ -187,13 +204,15 @@ int main(int argc, char **argv)
187204
printf(
188205
"#checks: %u, #inserts: %u, #removes: %u, "
189206
"cmap elements: %u, utilization: %.2lf \n",
190-
(uint32_t) current_checks, inserts, removes,
207+
(uint32_t) current_checks,
208+
atomic_load_explicit(&inserts, memory_order_relaxed),
209+
atomic_load_explicit(&removes, memory_order_relaxed),
191210
(uint32_t) cmap_size(&cmap_values), cmap_utilization(&cmap_values));
192211
usleep(250e3);
193212
}
194213

195214
/* Stop threads */
196-
running = false;
215+
atomic_store_explicit(&running, false, memory_order_relaxed);
197216
for (int i = 0; i <= readers; ++i)
198217
pthread_join(threads[i], NULL);
199218

0 commit comments

Comments
 (0)