test_threads.c (8370B)
1 /* Copyright (c) 2001-2004, Roger Dingledine. 2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 3 * Copyright (c) 2007-2021, The Tor Project, Inc. */ 4 /* See LICENSE for licensing information */ 5 6 #include "orconfig.h" 7 #include "core/or/or.h" 8 #include "lib/thread/threads.h" 9 #include "test/test.h" 10 11 /** mutex for thread test to stop the threads hitting data at the same time. */ 12 static tor_mutex_t *thread_test_mutex_ = NULL; 13 /** mutexes for the thread test to make sure that the threads have to 14 * interleave somewhat. */ 15 static tor_mutex_t *thread_test_start1_ = NULL, 16 *thread_test_start2_ = NULL; 17 /** Shared strmap for the thread test. */ 18 static strmap_t *thread_test_strmap_ = NULL; 19 /** The name of thread1 for the thread test */ 20 static char *thread1_name_ = NULL; 21 /** The name of thread2 for the thread test */ 22 static char *thread2_name_ = NULL; 23 24 static int thread_fns_failed = 0; 25 26 static unsigned long thread_fn_tid1, thread_fn_tid2; 27 28 static void thread_test_func_(void* _s) ATTR_NORETURN; 29 30 /** How many iterations have the threads in the unit test run? */ 31 static tor_threadlocal_t count; 32 33 /** Helper function for threading unit tests: This function runs in a 34 * subthread. It grabs its own mutex (start1 or start2) to make sure that it 35 * should start, then it repeatedly alters _test_thread_strmap protected by 36 * thread_test_mutex_. */ 37 static void 38 thread_test_func_(void* _s) 39 { 40 char *s = _s; 41 int i; 42 tor_mutex_t *m; 43 char buf[64]; 44 char **cp; 45 int *mycount = tor_malloc_zero(sizeof(int)); 46 tor_threadlocal_set(&count, mycount); 47 if (!strcmp(s, "thread 1")) { 48 m = thread_test_start1_; 49 cp = &thread1_name_; 50 thread_fn_tid1 = tor_get_thread_id(); 51 } else { 52 m = thread_test_start2_; 53 cp = &thread2_name_; 54 thread_fn_tid2 = tor_get_thread_id(); 55 } 56 57 tor_snprintf(buf, sizeof(buf), "%lu", tor_get_thread_id()); 58 *cp = tor_strdup(buf); 59 60 tor_mutex_acquire(m); 61 62 for (i=0; i<10000; ++i) { 63 tor_mutex_acquire(thread_test_mutex_); 64 strmap_set(thread_test_strmap_, "last to run", *cp); 65 tor_mutex_release(thread_test_mutex_); 66 int *tls_count = tor_threadlocal_get(&count); 67 tor_assert(tls_count == mycount); 68 ++*tls_count; 69 } 70 tor_mutex_acquire(thread_test_mutex_); 71 strmap_set(thread_test_strmap_, s, *cp); 72 if (in_main_thread()) 73 ++thread_fns_failed; 74 tor_mutex_release(thread_test_mutex_); 75 76 tor_free(mycount); 77 78 tor_mutex_release(m); 79 80 spawn_exit(); 81 } 82 83 /** Run unit tests for threading logic. */ 84 static void 85 test_threads_basic(void *arg) 86 { 87 char *s1 = NULL, *s2 = NULL; 88 int done = 0, timedout = 0; 89 time_t started; 90 (void) arg; 91 tt_int_op(tor_threadlocal_init(&count), OP_EQ, 0); 92 93 set_main_thread(); 94 95 thread_test_mutex_ = tor_mutex_new(); 96 thread_test_start1_ = tor_mutex_new(); 97 thread_test_start2_ = tor_mutex_new(); 98 thread_test_strmap_ = strmap_new(); 99 s1 = tor_strdup("thread 1"); 100 s2 = tor_strdup("thread 2"); 101 tor_mutex_acquire(thread_test_start1_); 102 tor_mutex_acquire(thread_test_start2_); 103 spawn_func(thread_test_func_, s1); 104 spawn_func(thread_test_func_, s2); 105 tor_mutex_release(thread_test_start2_); 106 tor_mutex_release(thread_test_start1_); 107 started = time(NULL); 108 while (!done) { 109 tor_mutex_acquire(thread_test_mutex_); 110 strmap_assert_ok(thread_test_strmap_); 111 if (strmap_get(thread_test_strmap_, "thread 1") && 112 strmap_get(thread_test_strmap_, "thread 2")) { 113 done = 1; 114 } else if (time(NULL) > started + 150) { 115 timedout = done = 1; 116 } 117 tor_mutex_release(thread_test_mutex_); 118 /* Prevent the main thread from starving the worker threads. */ 119 tor_sleep_msec(10); 120 } 121 tor_mutex_acquire(thread_test_start1_); 122 tor_mutex_release(thread_test_start1_); 123 tor_mutex_acquire(thread_test_start2_); 124 tor_mutex_release(thread_test_start2_); 125 126 tor_mutex_free(thread_test_mutex_); 127 128 if (timedout) { 129 tt_assert(strmap_get(thread_test_strmap_, "thread 1")); 130 tt_assert(strmap_get(thread_test_strmap_, "thread 2")); 131 tt_assert(!timedout); 132 } 133 134 /* different thread IDs. */ 135 tt_assert(strcmp(strmap_get(thread_test_strmap_, "thread 1"), 136 strmap_get(thread_test_strmap_, "thread 2"))); 137 tt_assert(!strcmp(strmap_get(thread_test_strmap_, "thread 1"), 138 strmap_get(thread_test_strmap_, "last to run")) || 139 !strcmp(strmap_get(thread_test_strmap_, "thread 2"), 140 strmap_get(thread_test_strmap_, "last to run"))); 141 142 tt_int_op(thread_fns_failed, OP_EQ, 0); 143 tt_int_op(thread_fn_tid1, OP_NE, thread_fn_tid2); 144 145 done: 146 tor_free(s1); 147 tor_free(s2); 148 tor_free(thread1_name_); 149 tor_free(thread2_name_); 150 if (thread_test_strmap_) 151 strmap_free(thread_test_strmap_, NULL); 152 if (thread_test_start1_) 153 tor_mutex_free(thread_test_start1_); 154 if (thread_test_start2_) 155 tor_mutex_free(thread_test_start2_); 156 } 157 158 typedef struct cv_testinfo_t { 159 tor_cond_t *cond; 160 tor_mutex_t *mutex; 161 int value; 162 int addend; 163 int shutdown; 164 int n_shutdown; 165 int n_wakeups; 166 int n_timeouts; 167 int n_threads; 168 const struct timeval *tv; 169 } cv_testinfo_t; 170 171 static cv_testinfo_t * 172 cv_testinfo_new(void) 173 { 174 cv_testinfo_t *i = tor_malloc_zero(sizeof(*i)); 175 i->cond = tor_cond_new(); 176 i->mutex = tor_mutex_new_nonrecursive(); 177 return i; 178 } 179 180 static void 181 cv_testinfo_free(cv_testinfo_t *i) 182 { 183 if (!i) 184 return; 185 tor_cond_free(i->cond); 186 tor_mutex_free(i->mutex); 187 tor_free(i); 188 } 189 190 static void cv_test_thr_fn_(void *arg) ATTR_NORETURN; 191 192 static void 193 cv_test_thr_fn_(void *arg) 194 { 195 cv_testinfo_t *i = arg; 196 int tid, r; 197 198 tor_mutex_acquire(i->mutex); 199 tid = i->n_threads++; 200 tor_mutex_release(i->mutex); 201 (void) tid; 202 203 tor_mutex_acquire(i->mutex); 204 while (1) { 205 if (i->addend) { 206 i->value += i->addend; 207 i->addend = 0; 208 } 209 210 if (i->shutdown) { 211 ++i->n_shutdown; 212 i->shutdown = 0; 213 tor_mutex_release(i->mutex); 214 spawn_exit(); 215 } 216 r = tor_cond_wait(i->cond, i->mutex, i->tv); 217 ++i->n_wakeups; 218 if (r == 1) { 219 ++i->n_timeouts; 220 tor_mutex_release(i->mutex); 221 spawn_exit(); 222 } 223 } 224 } 225 226 static void 227 test_threads_conditionvar(void *arg) 228 { 229 cv_testinfo_t *ti=NULL; 230 const struct timeval msec100 = { 0, 100*1000 }; 231 const int timeout = !strcmp(arg, "tv"); 232 233 ti = cv_testinfo_new(); 234 if (timeout) { 235 ti->tv = &msec100; 236 } 237 238 #define SPIN_UNTIL(condition,sleep_msec) \ 239 while (1) { \ 240 tor_mutex_acquire(ti->mutex); \ 241 if (condition) { \ 242 break; \ 243 } \ 244 tor_mutex_release(ti->mutex); \ 245 tor_sleep_msec(sleep_msec); \ 246 } 247 248 spawn_func(cv_test_thr_fn_, ti); 249 spawn_func(cv_test_thr_fn_, ti); 250 spawn_func(cv_test_thr_fn_, ti); 251 spawn_func(cv_test_thr_fn_, ti); 252 253 SPIN_UNTIL(ti->n_threads == 4, 10); 254 255 time_t started_at = time(NULL); 256 257 ti->addend = 7; 258 ti->shutdown = 1; 259 tor_cond_signal_one(ti->cond); 260 tor_mutex_release(ti->mutex); 261 262 #define SPIN() \ 263 SPIN_UNTIL(ti->addend == 0, 0) 264 265 SPIN(); 266 267 ti->addend = 30; 268 ti->shutdown = 1; 269 tor_cond_signal_all(ti->cond); 270 tor_mutex_release(ti->mutex); 271 SPIN(); 272 273 ti->addend = 1000; 274 if (! timeout) ti->shutdown = 1; 275 tor_cond_signal_one(ti->cond); 276 tor_mutex_release(ti->mutex); 277 SPIN(); 278 ti->addend = 300; 279 if (! timeout) ti->shutdown = 1; 280 tor_cond_signal_all(ti->cond); 281 tor_mutex_release(ti->mutex); 282 283 SPIN(); 284 tor_mutex_release(ti->mutex); 285 286 tt_int_op(ti->value, OP_EQ, 1337); 287 if (!timeout) { 288 tt_int_op(ti->n_shutdown, OP_EQ, 4); 289 } else { 290 const int GIVE_UP_AFTER_SEC = 30; 291 SPIN_UNTIL((ti->n_timeouts == 2 || 292 time(NULL) >= started_at + GIVE_UP_AFTER_SEC), 10); 293 tt_int_op(ti->n_shutdown, OP_EQ, 2); 294 tt_int_op(ti->n_timeouts, OP_EQ, 2); 295 tor_mutex_release(ti->mutex); 296 } 297 298 done: 299 cv_testinfo_free(ti); 300 } 301 302 #define THREAD_TEST(name) \ 303 { #name, test_threads_##name, TT_FORK, NULL, NULL } 304 305 struct testcase_t thread_tests[] = { 306 THREAD_TEST(basic), 307 { "conditionvar", test_threads_conditionvar, TT_FORK, 308 &passthrough_setup, (void*)"no-tv" }, 309 { "conditionvar_timeout", test_threads_conditionvar, TT_FORK, 310 &passthrough_setup, (void*)"tv" }, 311 END_OF_TESTCASES 312 };