Skip to content

Commit a048e33

Browse files
committed
Fix data race issues detected by TSAN for cmap
1 parent b0b56b0 commit a048e33

File tree

5 files changed

+47
-20
lines changed

5 files changed

+47
-20
lines changed

cmap/Makefile

Lines changed: 7 additions & 1 deletion
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

Lines changed: 6 additions & 5 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 1 deletion
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.c

Lines changed: 30 additions & 11 deletions
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 to free each chunk of memory
138+
* ealier when we know that it won't be accessed by any thread anymore. */
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)