Skip to content

Commit 884d892

Browse files
committed
Harden against aba problem
1 parent 610c951 commit 884d892

File tree

8 files changed

+212
-85
lines changed

8 files changed

+212
-85
lines changed

build/run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ UNAMES=$(uname -s)
44
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
55
exec ape "$@"
66
else
7-
exec "$@"
7+
exec rusage "$@"
88
fi

examples/aba.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#if 0
2+
/*─────────────────────────────────────────────────────────────────╗
3+
│ To the extent possible under law, Justine Tunney has waived │
4+
│ all copyright and related or neighboring rights to this file, │
5+
│ as it is written in the following disclaimers: │
6+
│ • http://unlicense.org/ │
7+
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8+
╚─────────────────────────────────────────────────────────────────*/
9+
#endif
10+
#include <assert.h>
11+
#include <cosmo.h>
12+
#include <pthread.h>
13+
#include <stdatomic.h>
14+
#include <stdbool.h>
15+
#include <stdio.h>
16+
#include <stdlib.h>
17+
#include <string.h>
18+
19+
// lockless push / pop tutorial
20+
//
21+
// this file demonstrates how to create a singly linked list that can be
22+
// pushed and popped across multiple threads, using only atomics. atomic
23+
// operations (rather than using a mutex) make push/pop go faster and it
24+
// ensures asynchronous signal safety too. therefore it will be safe for
25+
// use in a variety of contexts, such as signal handlers.
26+
27+
#define THREADS 128
28+
#define ITERATIONS 10000
29+
30+
// adjust mask based on alignment of list struct
31+
//
32+
// - 0x00fffffffffffff0 may be used if List* is always 16-byte aligned.
33+
// We know that's the case here, because we call malloc() to create
34+
// every List* object, and malloc() results are always max aligned.
35+
//
36+
// - 0x00fffffffffffff8 may be used if List* is always 8-byte aligned.
37+
// This might be the case if you're pushing and popping stuff that was
38+
// allocated from an array, to avoid malloc() calls. This has one
39+
// fewer byte of safeguards against the ABA problem though.
40+
//
41+
// - 0x00fffffffffff000 may be used if List* is always page aligned.
42+
// This is a good choice if you use mmap() to allocate each List*
43+
// element, since it offers maximum protection against ABA.
44+
//
45+
// - only the highest byte of a 64-bit pointer is safe to use on our
46+
// supported platforms. on most x86 and arm systems, it's possible to
47+
// use the top sixteen bits. however that's not the case on more
48+
// recent high end x86-64 systems that have pml5t.
49+
//
50+
#define MASQUE 0x00fffffffffffff0
51+
52+
#define PTR(x) ((uintptr_t)(x) & MASQUE)
53+
#define TAG(x) ROL((uintptr_t)(x) & ~MASQUE, 8)
54+
#define ABA(p, t) ((uintptr_t)(p) | (ROR((uintptr_t)(t), 8) & ~MASQUE))
55+
#define ROL(x, n) (((x) << (n)) | ((x) >> (64 - (n))))
56+
#define ROR(x, n) (((x) >> (n)) | ((x) << (64 - (n))))
57+
58+
struct List {
59+
struct List* next;
60+
int count;
61+
};
62+
63+
atomic_uintptr_t list;
64+
65+
void push(struct List* elem) {
66+
uintptr_t tip;
67+
assert(!TAG(elem));
68+
for (tip = atomic_load_explicit(&list, memory_order_relaxed);;) {
69+
elem->next = (struct List*)PTR(tip);
70+
if (atomic_compare_exchange_weak_explicit(
71+
&list, &tip, ABA(elem, TAG(tip) + 1), memory_order_release,
72+
memory_order_relaxed))
73+
break;
74+
pthread_pause_np();
75+
}
76+
}
77+
78+
struct List* pop(void) {
79+
uintptr_t tip;
80+
struct List* elem;
81+
tip = atomic_load_explicit(&list, memory_order_relaxed);
82+
while ((elem = (struct List*)PTR(tip))) {
83+
if (atomic_compare_exchange_weak_explicit(
84+
&list, &tip, ABA(elem->next, TAG(tip) + 1), memory_order_acquire,
85+
memory_order_relaxed))
86+
break;
87+
pthread_pause_np();
88+
}
89+
return elem;
90+
}
91+
92+
void* tester(void* arg) {
93+
struct List* elem;
94+
for (int i = 0; i < ITERATIONS; ++i) {
95+
while (!(elem = pop())) {
96+
elem = malloc(sizeof(*elem));
97+
elem->count = 0;
98+
push(elem);
99+
}
100+
elem->count++;
101+
push(elem);
102+
}
103+
return 0;
104+
}
105+
106+
int main() {
107+
printf("testing aba problem...");
108+
fflush(stdout);
109+
pthread_t th[THREADS];
110+
for (int i = 0; i < THREADS; ++i)
111+
pthread_create(&th[i], 0, tester, 0);
112+
for (int i = 0; i < THREADS; ++i)
113+
pthread_join(th[i], 0);
114+
int sum = 0;
115+
struct List* elem;
116+
while ((elem = pop())) {
117+
printf(" %d", elem->count);
118+
sum += elem->count;
119+
free(elem);
120+
}
121+
printf("\n");
122+
assert(sum == ITERATIONS * THREADS);
123+
printf("you are the dancing queen\n");
124+
CheckForMemoryLeaks();
125+
}

libc/intrin/atomic.c

Lines changed: 0 additions & 24 deletions
This file was deleted.

libc/intrin/maps.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct Map {
2929
struct Maps {
3030
struct Tree *maps;
3131
_Atomic(uint64_t) lock;
32-
_Atomic(struct Map *) freed;
32+
_Atomic(uintptr_t) freed;
3333
size_t count;
3434
size_t pages;
3535
_Atomic(char *) pick;

libc/intrin/mmap.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@
5050

5151
#define PGUP(x) (((x) + pagesz - 1) & -pagesz)
5252

53+
#define MASQUE 0x00fffffffffffff8
54+
#define PTR(x) ((uintptr_t)(x) & MASQUE)
55+
#define TAG(x) ROL((uintptr_t)(x) & ~MASQUE, 8)
56+
#define ABA(p, t) ((uintptr_t)(p) | (ROR((uintptr_t)(t), 8) & ~MASQUE))
57+
#define ROL(x, n) (((x) << (n)) | ((x) >> (64 - (n))))
58+
#define ROR(x, n) (((x) >> (n)) | ((x) << (64 - (n))))
59+
5360
#if !MMDEBUG
5461
#define ASSERT(x) (void)0
5562
#else
@@ -227,14 +234,17 @@ static int __muntrack(char *addr, size_t size, int pagesz,
227234
}
228235

229236
void __maps_free(struct Map *map) {
237+
uintptr_t tip;
238+
ASSERT(!TAG(map));
230239
map->size = 0;
231240
map->addr = MAP_FAILED;
232-
map->freed = atomic_load_explicit(&__maps.freed, memory_order_relaxed);
233-
for (;;) {
234-
if (atomic_compare_exchange_weak_explicit(&__maps.freed, &map->freed, map,
235-
memory_order_release,
236-
memory_order_relaxed))
241+
for (tip = atomic_load_explicit(&__maps.freed, memory_order_relaxed);;) {
242+
map->freed = (struct Map *)PTR(tip);
243+
if (atomic_compare_exchange_weak_explicit(
244+
&__maps.freed, &tip, ABA(map, TAG(tip) + 1), memory_order_release,
245+
memory_order_relaxed))
237246
break;
247+
pthread_pause_np();
238248
}
239249
}
240250

@@ -297,12 +307,13 @@ void __maps_insert(struct Map *map) {
297307

298308
struct Map *__maps_alloc(void) {
299309
struct Map *map;
300-
map = atomic_load_explicit(&__maps.freed, memory_order_relaxed);
301-
while (map) {
302-
if (atomic_compare_exchange_weak_explicit(&__maps.freed, &map, map->freed,
303-
memory_order_acquire,
304-
memory_order_relaxed))
310+
uintptr_t tip = atomic_load_explicit(&__maps.freed, memory_order_relaxed);
311+
while ((map = (struct Map *)PTR(tip))) {
312+
if (atomic_compare_exchange_weak_explicit(
313+
&__maps.freed, &tip, ABA(map->freed, TAG(tip) + 1),
314+
memory_order_acquire, memory_order_relaxed))
305315
return map;
316+
pthread_pause_np();
306317
}
307318
int gransz = __gransize;
308319
struct DirectMap sys = sys_mmap(0, gransz, PROT_READ | PROT_WRITE,

net/turfwar/turfwar.c

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -378,25 +378,25 @@ struct timespec WaitFor(int millis) {
378378
bool CheckMem(const char *file, int line, void *ptr) {
379379
if (ptr)
380380
return true;
381-
kprintf("%s:%d: %P: out of memory: %s\n", file, line, strerror(errno));
381+
kprintf("%s:%d: %H: out of memory: %s\n", file, line, strerror(errno));
382382
return false;
383383
}
384384
bool CheckSys(const char *file, int line, long rc) {
385385
if (rc != -1)
386386
return true;
387-
kprintf("%s:%d: %P: %s\n", file, line, strerror(errno));
387+
kprintf("%s:%d: %H: %s\n", file, line, strerror(errno));
388388
return false;
389389
}
390390
bool CheckSql(const char *file, int line, int rc) {
391391
if (rc == SQLITE_OK)
392392
return true;
393-
kprintf("%s:%d: %P: %s\n", file, line, sqlite3_errstr(rc));
393+
kprintf("%s:%d: %H: %s\n", file, line, sqlite3_errstr(rc));
394394
return false;
395395
}
396396
bool CheckDb(const char *file, int line, int rc, sqlite3 *db) {
397397
if (rc == SQLITE_OK)
398398
return true;
399-
kprintf("%s:%d: %P: %s: %s\n", file, line, sqlite3_errstr(rc),
399+
kprintf("%s:%d: %H: %s: %s\n", file, line, sqlite3_errstr(rc),
400400
sqlite3_errmsg(db));
401401
return false;
402402
}
@@ -1645,7 +1645,7 @@ bool GenerateScore(struct Asset *out, long secs, long cash) {
16451645
void *ScoreWorker(void *arg) {
16461646
pthread_setname_np(pthread_self(), "ScoreAll");
16471647
for (;;) {
1648-
LOG("%P regenerating score...\n");
1648+
LOG("%H regenerating score...\n");
16491649
Update(&g_asset.score, GenerateScore, -1, MS2CASH(SCORE_UPDATE_MS));
16501650
usleep(SCORE_UPDATE_MS * 1000);
16511651
}
@@ -1655,9 +1655,9 @@ void *ScoreWorker(void *arg) {
16551655
void *ScoreHourWorker(void *arg) {
16561656
pthread_setname_np(pthread_self(), "ScoreHour");
16571657
for (;;) {
1658-
LOG("%P regenerating hour score...\n");
1658+
LOG("%H regenerating hour score...\n");
16591659
Update(&g_asset.score_hour, GenerateScore, 60L * 60,
1660-
MS2CASH(SCORE_UPDATE_MS));
1660+
MS2CASH(SCORE_H_UPDATE_MS));
16611661
usleep(SCORE_H_UPDATE_MS * 1000);
16621662
}
16631663
}
@@ -1666,7 +1666,7 @@ void *ScoreHourWorker(void *arg) {
16661666
void *ScoreDayWorker(void *arg) {
16671667
pthread_setname_np(pthread_self(), "ScoreDay");
16681668
for (;;) {
1669-
LOG("%P regenerating day score...\n");
1669+
LOG("%H regenerating day score...\n");
16701670
Update(&g_asset.score_day, GenerateScore, 60L * 60 * 24,
16711671
MS2CASH(SCORE_D_UPDATE_MS));
16721672
usleep(SCORE_D_UPDATE_MS * 1000);
@@ -1677,7 +1677,7 @@ void *ScoreDayWorker(void *arg) {
16771677
void *ScoreWeekWorker(void *arg) {
16781678
pthread_setname_np(pthread_self(), "ScoreWeek");
16791679
for (;;) {
1680-
LOG("%P regenerating week score...\n");
1680+
LOG("%H regenerating week score...\n");
16811681
Update(&g_asset.score_week, GenerateScore, 60L * 60 * 24 * 7,
16821682
MS2CASH(SCORE_W_UPDATE_MS));
16831683
usleep(SCORE_W_UPDATE_MS * 1000);
@@ -1688,7 +1688,7 @@ void *ScoreWeekWorker(void *arg) {
16881688
void *ScoreMonthWorker(void *arg) {
16891689
pthread_setname_np(pthread_self(), "ScoreMonth");
16901690
for (;;) {
1691-
LOG("%P regenerating month score...\n");
1691+
LOG("%H regenerating month score...\n");
16921692
Update(&g_asset.score_month, GenerateScore, 60L * 60 * 24 * 30,
16931693
MS2CASH(SCORE_M_UPDATE_MS));
16941694
usleep(SCORE_M_UPDATE_MS * 1000);
@@ -1874,7 +1874,7 @@ void Meltdown(void) {
18741874
int i, marks;
18751875
struct timespec now;
18761876
++g_meltdowns;
1877-
LOG("%P panicking because %d out of %d workers is connected\n", g_connections,
1877+
LOG("%H panicking because %d out of %d workers is connected\n", g_connections,
18781878
g_workers);
18791879
now = timespec_real();
18801880
for (marks = i = 0; i < g_workers; ++i) {
@@ -1924,9 +1924,9 @@ void CheckDatabase(void) {
19241924
sqlite3 *db;
19251925
if (g_integrity) {
19261926
CHECK_SQL(DbOpen("db.sqlite3", &db));
1927-
LOG("%P Checking database integrity...\n");
1927+
LOG("%H Checking database integrity...\n");
19281928
CHECK_SQL(sqlite3_exec(db, "PRAGMA integrity_check", 0, 0, 0));
1929-
LOG("%P Vacuuming database...\n");
1929+
LOG("%H Vacuuming database...\n");
19301930
CHECK_SQL(sqlite3_exec(db, "VACUUM", 0, 0, 0));
19311931
CHECK_SQL(sqlite3_close(db));
19321932
}
@@ -2204,11 +2204,11 @@ int main(int argc, char *argv[]) {
22042204
pthread_attr_destroy(&attr);
22052205

22062206
// time to serve
2207-
LOG("%P ready\n");
2207+
LOG("%H ready\n");
22082208
Supervisor(0);
22092209

22102210
// cancel listen()
2211-
LOG("%P interrupting services...\n");
2211+
LOG("%H interrupting services...\n");
22122212
pthread_cancel(scorer);
22132213
pthread_cancel(recenter);
22142214
pthread_cancel(g_listener);
@@ -2218,7 +2218,7 @@ int main(int argc, char *argv[]) {
22182218
pthread_cancel(scorer_month);
22192219
pthread_cancel(replenisher);
22202220

2221-
LOG("%P joining services...\n");
2221+
LOG("%H joining services...\n");
22222222
unassert(!pthread_join(scorer, 0));
22232223
unassert(!pthread_join(recenter, 0));
22242224
unassert(!pthread_join(g_listener, 0));
@@ -2229,13 +2229,13 @@ int main(int argc, char *argv[]) {
22292229
unassert(!pthread_join(replenisher, 0));
22302230

22312231
// cancel read() so that keepalive clients finish faster
2232-
LOG("%P interrupting workers...\n");
2232+
LOG("%H interrupting workers...\n");
22332233
for (int i = 0; i < g_workers; ++i)
22342234
if (!g_worker[i].dead)
22352235
pthread_cancel(g_worker[i].th);
22362236

22372237
// wait for producers to finish
2238-
LOG("%P joining workers...\n");
2238+
LOG("%H joining workers...\n");
22392239
for (int i = 0; i < g_workers; ++i)
22402240
unassert(!pthread_join(g_worker[i].th, 0));
22412241

@@ -2244,14 +2244,14 @@ int main(int argc, char *argv[]) {
22442244
// claims worker thread which waits forever for new claims.
22452245
unassert(!g_claims.count);
22462246
pthread_cancel(claimer);
2247-
LOG("%P waiting for claims worker...\n");
2247+
LOG("%H waiting for claims worker...\n");
22482248
unassert(!pthread_join(claimer, 0));
22492249

22502250
// perform some sanity checks
22512251
unassert(g_claimsprocessed == g_claimsenqueued);
22522252

22532253
// free memory
2254-
LOG("%P freeing memory...\n");
2254+
LOG("%H freeing memory...\n");
22552255
FreeAsset(&g_asset.user);
22562256
FreeAsset(&g_asset.about);
22572257
FreeAsset(&g_asset.index);
@@ -2267,6 +2267,6 @@ int main(int argc, char *argv[]) {
22672267

22682268
time_destroy();
22692269

2270-
LOG("%P goodbye\n");
2270+
LOG("%H goodbye\n");
22712271
// CheckForMemoryLeaks();
22722272
}

0 commit comments

Comments
 (0)