Line data Source code
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 2 : thread_test_func_(void* _s)
39 : {
40 2 : char *s = _s;
41 2 : int i;
42 2 : tor_mutex_t *m;
43 2 : char buf[64];
44 2 : char **cp;
45 2 : int *mycount = tor_malloc_zero(sizeof(int));
46 2 : tor_threadlocal_set(&count, mycount);
47 2 : if (!strcmp(s, "thread 1")) {
48 1 : m = thread_test_start1_;
49 1 : cp = &thread1_name_;
50 1 : thread_fn_tid1 = tor_get_thread_id();
51 : } else {
52 1 : m = thread_test_start2_;
53 1 : cp = &thread2_name_;
54 1 : thread_fn_tid2 = tor_get_thread_id();
55 : }
56 :
57 2 : tor_snprintf(buf, sizeof(buf), "%lu", tor_get_thread_id());
58 2 : *cp = tor_strdup(buf);
59 :
60 2 : tor_mutex_acquire(m);
61 :
62 20001 : for (i=0; i<10000; ++i) {
63 19997 : tor_mutex_acquire(thread_test_mutex_);
64 20000 : strmap_set(thread_test_strmap_, "last to run", *cp);
65 20000 : tor_mutex_release(thread_test_mutex_);
66 19997 : int *tls_count = tor_threadlocal_get(&count);
67 19997 : tor_assert(tls_count == mycount);
68 19997 : ++*tls_count;
69 : }
70 2 : tor_mutex_acquire(thread_test_mutex_);
71 2 : strmap_set(thread_test_strmap_, s, *cp);
72 2 : if (in_main_thread())
73 0 : ++thread_fns_failed;
74 2 : tor_mutex_release(thread_test_mutex_);
75 :
76 2 : tor_free(mycount);
77 :
78 2 : tor_mutex_release(m);
79 :
80 2 : spawn_exit();
81 : }
82 :
83 : /** Run unit tests for threading logic. */
84 : static void
85 1 : test_threads_basic(void *arg)
86 : {
87 1 : char *s1 = NULL, *s2 = NULL;
88 1 : int done = 0, timedout = 0;
89 1 : time_t started;
90 1 : (void) arg;
91 1 : tt_int_op(tor_threadlocal_init(&count), OP_EQ, 0);
92 :
93 1 : set_main_thread();
94 :
95 1 : thread_test_mutex_ = tor_mutex_new();
96 1 : thread_test_start1_ = tor_mutex_new();
97 1 : thread_test_start2_ = tor_mutex_new();
98 1 : thread_test_strmap_ = strmap_new();
99 1 : s1 = tor_strdup("thread 1");
100 1 : s2 = tor_strdup("thread 2");
101 1 : tor_mutex_acquire(thread_test_start1_);
102 1 : tor_mutex_acquire(thread_test_start2_);
103 1 : spawn_func(thread_test_func_, s1);
104 1 : spawn_func(thread_test_func_, s2);
105 1 : tor_mutex_release(thread_test_start2_);
106 1 : tor_mutex_release(thread_test_start1_);
107 1 : started = time(NULL);
108 3 : while (!done) {
109 2 : tor_mutex_acquire(thread_test_mutex_);
110 2 : strmap_assert_ok(thread_test_strmap_);
111 3 : if (strmap_get(thread_test_strmap_, "thread 1") &&
112 1 : strmap_get(thread_test_strmap_, "thread 2")) {
113 : done = 1;
114 1 : } else if (time(NULL) > started + 150) {
115 0 : timedout = done = 1;
116 : }
117 2 : tor_mutex_release(thread_test_mutex_);
118 : /* Prevent the main thread from starving the worker threads. */
119 2 : tor_sleep_msec(10);
120 : }
121 1 : tor_mutex_acquire(thread_test_start1_);
122 1 : tor_mutex_release(thread_test_start1_);
123 1 : tor_mutex_acquire(thread_test_start2_);
124 1 : tor_mutex_release(thread_test_start2_);
125 :
126 1 : tor_mutex_free(thread_test_mutex_);
127 :
128 1 : if (timedout) {
129 0 : tt_assert(strmap_get(thread_test_strmap_, "thread 1"));
130 0 : tt_assert(strmap_get(thread_test_strmap_, "thread 2"));
131 0 : tt_assert(!timedout);
132 : }
133 :
134 : /* different thread IDs. */
135 1 : tt_assert(strcmp(strmap_get(thread_test_strmap_, "thread 1"),
136 : strmap_get(thread_test_strmap_, "thread 2")));
137 1 : 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 1 : tt_int_op(thread_fns_failed, OP_EQ, 0);
143 1 : tt_int_op(thread_fn_tid1, OP_NE, thread_fn_tid2);
144 :
145 1 : done:
146 1 : tor_free(s1);
147 1 : tor_free(s2);
148 1 : tor_free(thread1_name_);
149 1 : tor_free(thread2_name_);
150 1 : if (thread_test_strmap_)
151 1 : strmap_free(thread_test_strmap_, NULL);
152 1 : if (thread_test_start1_)
153 1 : tor_mutex_free(thread_test_start1_);
154 1 : if (thread_test_start2_)
155 1 : tor_mutex_free(thread_test_start2_);
156 1 : }
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 2 : cv_testinfo_new(void)
173 : {
174 2 : cv_testinfo_t *i = tor_malloc_zero(sizeof(*i));
175 2 : i->cond = tor_cond_new();
176 2 : i->mutex = tor_mutex_new_nonrecursive();
177 2 : return i;
178 : }
179 :
180 : static void
181 2 : cv_testinfo_free(cv_testinfo_t *i)
182 : {
183 2 : if (!i)
184 : return;
185 2 : tor_cond_free(i->cond);
186 2 : tor_mutex_free(i->mutex);
187 2 : tor_free(i);
188 : }
189 :
190 : static void cv_test_thr_fn_(void *arg) ATTR_NORETURN;
191 :
192 : static void
193 8 : cv_test_thr_fn_(void *arg)
194 : {
195 8 : cv_testinfo_t *i = arg;
196 8 : int tid, r;
197 :
198 8 : tor_mutex_acquire(i->mutex);
199 8 : tid = i->n_threads++;
200 8 : tor_mutex_release(i->mutex);
201 8 : (void) tid;
202 :
203 8 : tor_mutex_acquire(i->mutex);
204 21 : while (1) {
205 21 : if (i->addend) {
206 8 : i->value += i->addend;
207 8 : i->addend = 0;
208 : }
209 :
210 21 : if (i->shutdown) {
211 6 : ++i->n_shutdown;
212 6 : i->shutdown = 0;
213 6 : tor_mutex_release(i->mutex);
214 6 : spawn_exit();
215 : }
216 15 : r = tor_cond_wait(i->cond, i->mutex, i->tv);
217 15 : ++i->n_wakeups;
218 15 : if (r == 1) {
219 2 : ++i->n_timeouts;
220 2 : tor_mutex_release(i->mutex);
221 2 : spawn_exit();
222 : }
223 : }
224 : }
225 :
226 : static void
227 2 : test_threads_conditionvar(void *arg)
228 : {
229 2 : cv_testinfo_t *ti=NULL;
230 2 : const struct timeval msec100 = { 0, 100*1000 };
231 2 : const int timeout = !strcmp(arg, "tv");
232 :
233 2 : ti = cv_testinfo_new();
234 2 : if (timeout) {
235 1 : 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 2 : spawn_func(cv_test_thr_fn_, ti);
249 2 : spawn_func(cv_test_thr_fn_, ti);
250 2 : spawn_func(cv_test_thr_fn_, ti);
251 2 : spawn_func(cv_test_thr_fn_, ti);
252 :
253 4 : SPIN_UNTIL(ti->n_threads == 4, 10);
254 :
255 2 : time_t started_at = time(NULL);
256 :
257 2 : ti->addend = 7;
258 2 : ti->shutdown = 1;
259 2 : tor_cond_signal_one(ti->cond);
260 2 : tor_mutex_release(ti->mutex);
261 :
262 : #define SPIN() \
263 : SPIN_UNTIL(ti->addend == 0, 0)
264 :
265 4 : SPIN();
266 :
267 2 : ti->addend = 30;
268 2 : ti->shutdown = 1;
269 2 : tor_cond_signal_all(ti->cond);
270 2 : tor_mutex_release(ti->mutex);
271 4 : SPIN();
272 :
273 2 : ti->addend = 1000;
274 2 : if (! timeout) ti->shutdown = 1;
275 2 : tor_cond_signal_one(ti->cond);
276 2 : tor_mutex_release(ti->mutex);
277 4 : SPIN();
278 2 : ti->addend = 300;
279 2 : if (! timeout) ti->shutdown = 1;
280 2 : tor_cond_signal_all(ti->cond);
281 2 : tor_mutex_release(ti->mutex);
282 :
283 4 : SPIN();
284 2 : tor_mutex_release(ti->mutex);
285 :
286 2 : tt_int_op(ti->value, OP_EQ, 1337);
287 2 : if (!timeout) {
288 1 : tt_int_op(ti->n_shutdown, OP_EQ, 4);
289 : } else {
290 : const int GIVE_UP_AFTER_SEC = 30;
291 11 : SPIN_UNTIL((ti->n_timeouts == 2 ||
292 1 : time(NULL) >= started_at + GIVE_UP_AFTER_SEC), 10);
293 1 : tt_int_op(ti->n_shutdown, OP_EQ, 2);
294 1 : tt_int_op(ti->n_timeouts, OP_EQ, 2);
295 1 : tor_mutex_release(ti->mutex);
296 : }
297 :
298 2 : done:
299 2 : cv_testinfo_free(ti);
300 2 : }
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 : };
|