source: mainline/uspace/app/rcutest/rcutest.c@ 29b8138

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 29b8138 was 29b8138, checked in by Adam Hraska <adam.hraska+hos@…>, 13 years ago

urcu: Added sanity tests.

  • Property mode set to 100644
File size: 12.4 KB
Line 
1/*
2 * Copyright (c) 2012 Adam Hraska
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup test
30 * @{
31 */
32
33/**
34 * @file rcutest.c
35 */
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <stdint.h>
40#include <mem.h>
41#include <errno.h>
42#include <thread.h>
43#include <assert.h>
44#include <async.h>
45#include <fibril.h>
46#include <compiler/barrier.h>
47
48#include <rcu.h>
49
50
51#define USECS_PER_SEC (1000 * 1000)
52#define USECS_PER_MS 1000
53
54
55typedef struct test_desc {
56 /* Aggregate test that runs other tests already in the table test_desc. */
57 bool aggregate;
58 enum {
59 T_OTHER,
60 T_SANITY,
61 T_STRESS
62 } type;
63 bool (*func)(void);
64 const char *name;
65 const char *desc;
66} test_desc_t;
67
68
69static bool run_all_tests(void);
70static bool run_sanity_tests(void);
71static bool run_stress_tests(void);
72
73static bool wait_for_one_reader(void);
74static bool basic_sanity_check(void);
75static bool dont_wait_for_new_reader(void);
76
77
78static test_desc_t test_desc[] = {
79 {
80 .aggregate = true,
81 .type = T_OTHER,
82 .func = run_all_tests,
83 .name = "*",
84 .desc = "Runs all tests.",
85 },
86 {
87 .aggregate = true,
88 .type = T_SANITY,
89 .func = run_sanity_tests,
90 .name = "sanity-tests",
91 .desc = "Runs all RCU sanity tests.",
92 },
93 {
94 .aggregate = true,
95 .type = T_STRESS,
96 .func = run_stress_tests,
97 .name = "stress-tests",
98 .desc = "Runs all RCU stress tests.",
99 },
100
101 {
102 .aggregate = false,
103 .type = T_SANITY,
104 .func = basic_sanity_check,
105 .name = "basic-sanity",
106 .desc = "Locks/unlocks and syncs in a single fibril, no contention.",
107 },
108 {
109 .aggregate = false,
110 .type = T_SANITY,
111 .func = wait_for_one_reader,
112 .name = "wait-for-one",
113 .desc = "Syncs with one 2 secs sleeping reader.",
114 },
115 {
116 .aggregate = false,
117 .type = T_SANITY,
118 .func = dont_wait_for_new_reader,
119 .name = "ignore-new-r",
120 .desc = "Syncs with preexisting reader but ignores new reader.",
121 },
122 {
123 .aggregate = false,
124 .type = T_OTHER,
125 .func = NULL,
126 .name = "(null)",
127 .desc = "",
128 },
129};
130
131static const size_t test_desc_cnt = sizeof(test_desc) / sizeof(test_desc[0]);
132
133/*--------------------------------------------------------------------*/
134
135typedef int (*fibril_func_t)(void *);
136
137static bool create_fibril(int (*func)(void*), void *arg)
138{
139 fid_t fid = fibril_create(func, arg);
140
141 if (0 == fid) {
142 printf("Failed to create a fibril!\n");
143 return false;
144 }
145
146 fibril_add_ready(fid);
147 return true;
148}
149
150/*--------------------------------------------------------------------*/
151
152static bool run_tests(bool (*include_filter)(test_desc_t *))
153{
154 size_t failed_cnt = 0;
155 size_t ok_cnt = 0;
156
157 for (size_t i = 0; i < test_desc_cnt; ++i) {
158 test_desc_t *t = &test_desc[i];
159
160 if (t->func && !t->aggregate && include_filter(t)) {
161 printf("Running \'%s\'...\n", t->name);
162 bool ok = test_desc[i].func();
163
164 if (ok) {
165 ++ok_cnt;
166 printf("Passed: \'%s\'\n", t->name);
167 } else {
168 ++failed_cnt;
169 printf("FAILED: \'%s\'\n", t->name);
170 }
171 }
172 }
173
174 printf("\n");
175
176 printf("%zu tests passed\n", ok_cnt);
177
178 if (failed_cnt) {
179 printf("%zu tests failed\n", failed_cnt);
180 }
181
182 return 0 == failed_cnt;
183}
184
185/*--------------------------------------------------------------------*/
186
187static bool all_tests_include_filter(test_desc_t *desc)
188{
189 return true;
190}
191
192/* Runs all available tests tests one-by-one. */
193static bool run_all_tests(void)
194{
195 printf("Running all tests...\n");
196 return run_tests(all_tests_include_filter);
197}
198
199/*--------------------------------------------------------------------*/
200
201static bool stress_tests_include_filter(test_desc_t *desc)
202{
203 return desc->type == T_STRESS;
204}
205
206/* Runs all available stress tests one-by-one. */
207static bool run_stress_tests(void)
208{
209 printf("Running stress tests...\n");
210 return run_tests(stress_tests_include_filter);
211}
212
213/*--------------------------------------------------------------------*/
214
215static bool sanity_tests_include_filter(test_desc_t *desc)
216{
217 return desc->type == T_SANITY;
218}
219
220/* Runs all available sanity tests one-by-one. */
221static bool run_sanity_tests(void)
222{
223 printf("Running sanity tests...\n");
224 return run_tests(sanity_tests_include_filter);
225}
226
227/*--------------------------------------------------------------------*/
228
229/* Locks/unlocks rcu and synchronizes without contention in a single fibril. */
230static bool basic_sanity_check(void)
231{
232 rcu_read_lock();
233 /* nop */
234 rcu_read_unlock();
235
236 rcu_read_lock();
237 /* nop */
238 rcu_read_unlock();
239
240 rcu_synchronize();
241
242 /* Nested lock with yield(). */
243 rcu_read_lock();
244 fibril_yield();
245 rcu_read_lock();
246 fibril_yield();
247 rcu_read_unlock();
248 fibril_yield();
249 rcu_read_unlock();
250
251 fibril_yield();
252 rcu_synchronize();
253 rcu_synchronize();
254
255 rcu_read_lock();
256 /* nop */
257 if (!rcu_read_locked())
258 return false;
259
260 rcu_read_unlock();
261
262 return !rcu_read_locked();
263}
264
265typedef struct one_reader_info {
266 bool entered_cs;
267 bool exited_cs;
268 size_t done_sleeps_cnt;
269 bool synching;
270 bool synched;
271} one_reader_info_t;
272
273
274static int sleeping_reader(one_reader_info_t *arg)
275{
276 rcu_register_fibril();
277
278 printf("lock{");
279 rcu_read_lock();
280 arg->entered_cs = true;
281
282 printf("r-sleep{");
283 /* 2 sec */
284 async_usleep(2 * USECS_PER_SEC);
285 ++arg->done_sleeps_cnt;
286 printf("}");
287
288 arg->exited_cs = true;
289 rcu_read_unlock();
290 printf("}");
291
292 rcu_deregister_fibril();
293 return 0;
294}
295
296static bool wait_for_one_reader(void)
297{
298 one_reader_info_t info = { 0 };
299
300 if (!create_fibril((fibril_func_t) sleeping_reader, &info))
301 return false;
302
303 /* 1 sec, waits for the reader to enter its critical section and sleep. */
304 async_usleep(1 * USECS_PER_SEC);
305
306 if (!info.entered_cs || info.exited_cs) {
307 printf("Error: reader is unexpectedly outside of critical section.\n");
308 return false;
309 }
310
311 info.synching = true;
312 printf("sync[");
313 rcu_synchronize();
314 printf("]\n");
315 info.synched = true;
316
317 /* Load info.exited_cs */
318 memory_barrier();
319
320 if (!info.exited_cs) {
321 printf("Error: rcu_sync() returned before the reader exited its CS.\n");
322 /*
323 * Sleep some more so we don't free info on stack while the reader
324 * is using it.
325 */
326 /* 1.5 sec */
327 async_usleep(1500 * 1000);
328 return false;
329 } else {
330 return true;
331 }
332}
333
334/*--------------------------------------------------------------------*/
335
336#define WAIT_STEP_US 1000 * USECS_PER_MS
337
338typedef struct two_reader_info {
339 bool new_entered_cs;
340 bool new_exited_cs;
341 bool old_entered_cs;
342 bool old_exited_cs;
343 bool synching;
344 bool synched;
345 size_t failed;
346} two_reader_info_t;
347
348
349static int preexisting_reader(two_reader_info_t *arg)
350{
351 rcu_register_fibril();
352
353 printf("old-lock{");
354 rcu_read_lock();
355 arg->old_entered_cs = true;
356
357 printf("wait-for-sync{");
358 /* Wait for rcu_sync() to start waiting for us. */
359 while (!arg->synching) {
360 async_usleep(WAIT_STEP_US);
361 }
362 printf(" }");
363
364 /* A new reader starts while rcu_sync() is in progress. */
365
366 printf("wait-for-new-R{");
367 /* Wait for the new reader to enter its reader section. */
368 while (!arg->new_entered_cs) {
369 async_usleep(WAIT_STEP_US);
370 }
371 printf(" }");
372
373 arg->old_exited_cs = true;
374
375 assert(!arg->new_exited_cs);
376
377 if (arg->synched) {
378 arg->failed = 1;
379 printf("Error: rcu_sync() did not wait for preexisting reader.\n");
380 }
381
382 rcu_read_unlock();
383 printf(" }");
384
385 rcu_deregister_fibril();
386 return 0;
387}
388
389static int new_reader(two_reader_info_t *arg)
390{
391 rcu_register_fibril();
392
393 /* Wait until rcu_sync() starts. */
394 while (!arg->synching) {
395 async_usleep(WAIT_STEP_US);
396 }
397
398 /*
399 * synching is set when rcu_sync() is about to be entered so wait
400 * some more to make sure it really does start executing.
401 */
402 async_usleep(WAIT_STEP_US);
403
404 printf("new-lock(");
405 rcu_read_lock();
406 arg->new_entered_cs = true;
407
408 /* Wait for rcu_sync() exit, ie stop waiting for the preexisting reader. */
409 while (!arg->synched) {
410 async_usleep(WAIT_STEP_US);
411 }
412
413 arg->new_exited_cs = true;
414 /* Write new_exited_cs before exiting reader section. */
415 memory_barrier();
416
417 /*
418 * Preexisting reader should have exited by now, so rcu_synchronize()
419 * must have returned.
420 */
421 if (!arg->old_exited_cs) {
422 arg->failed = 1;
423 printf("Error: preexisting reader should have exited by now!\n");
424 }
425
426 rcu_read_unlock();
427 printf(")");
428
429 rcu_deregister_fibril();
430 return 0;
431}
432
433static bool dont_wait_for_new_reader(void)
434{
435 two_reader_info_t info = { 0 };
436
437 if (!create_fibril((fibril_func_t) preexisting_reader, &info))
438 return false;
439
440 if (!create_fibril((fibril_func_t) new_reader, &info))
441 return false;
442
443 /* Waits for the preexisting_reader to enter its CS.*/
444 while (!info.old_entered_cs) {
445 async_usleep(WAIT_STEP_US);
446 }
447
448 assert(!info.old_exited_cs);
449 assert(!info.new_entered_cs);
450 assert(!info.new_exited_cs);
451
452 printf("sync[");
453 info.synching = true;
454 rcu_synchronize();
455 printf(" ]");
456
457 /* Load info.exited_cs */
458 memory_barrier();
459
460 if (!info.old_exited_cs) {
461 printf("Error: rcu_sync() returned before preexisting reader exited.\n");
462 info.failed = 1;
463 }
464
465 bool new_outside_cs = !info.new_entered_cs || info.new_exited_cs;
466
467 /* Test if new reader is waiting in CS before setting synched. */
468 compiler_barrier();
469 info.synched = true;
470
471 if (new_outside_cs) {
472 printf("Error: new reader CS held up rcu_sync(). (4)\n");
473 info.failed = 1;
474 } else {
475 /* Wait for the new reader. */
476 rcu_synchronize();
477
478 if (!info.new_exited_cs) {
479 printf("Error: 2nd rcu_sync() returned before new reader exited.\n");
480 info.failed = 1;
481 }
482
483 printf("\n");
484 }
485
486 if (info.failed) {
487 /*
488 * Sleep some more so we don't free info on stack while readers
489 * are using it.
490 */
491 async_usleep(WAIT_STEP_US);
492 }
493
494 return 0 == info.failed;
495}
496
497#undef WAIT_STEP_US
498/*--------------------------------------------------------------------*/
499/*--------------------------------------------------------------------*/
500static test_desc_t *find_test(const char *name)
501{
502 /* First match for test name. */
503 for (size_t k = 0; k < test_desc_cnt; ++k) {
504 test_desc_t *t = &test_desc[k];
505
506 if (t->func && 0 == str_cmp(t->name, name))
507 return t;
508 }
509
510 /* Try to match the test number. */
511 uint32_t test_num = 0;
512
513 if (EOK == str_uint32_t(name, NULL, 0, true, &test_num)) {
514 if (test_num < test_desc_cnt && test_desc[test_num].func) {
515 printf("[%u]\n", test_num);
516 return &test_desc[test_num];
517 }
518 }
519
520 return NULL;
521}
522
523static void list_tests(void)
524{
525 printf("Available tests: \n");
526
527 for (size_t i = 0; i < test_desc_cnt; ++i) {
528 test_desc_t *t = &test_desc[i];
529
530 if (!t->func)
531 continue;
532
533 const char *type = "";
534
535 if (t->type == T_SANITY)
536 type = " (sanity)";
537 if (t->type == T_STRESS)
538 type = " (stress)";
539
540 printf("%zu: %s ..%s %s\n", i, t->name, type, t->desc);
541 }
542}
543
544
545static void print_usage(void)
546{
547 printf("Usage: rcutest [test_name|test_number]\n");
548 list_tests();
549
550 printf("\nExample usage:\n");
551 printf("\trcutest *\n");
552 printf("\trcutest sanity-tests\n");
553}
554
555
556int main(int argc, char **argv)
557{
558 rcu_register_fibril();
559
560 if (argc != 2) {
561 print_usage();
562
563 rcu_deregister_fibril();
564 return 2;
565 }
566
567 test_desc_t *t = find_test(argv[1]);
568
569 if (t) {
570 printf("Running '%s'...\n", t->name);
571 bool ok = t->func();
572
573 printf("%s: '%s'\n", ok ? "Passed" : "FAILED", t->name);
574
575 rcu_deregister_fibril();
576 return ok ? 0 : 1;
577 } else {
578 printf("Non-existent test name.\n");
579 list_tests();
580
581 rcu_deregister_fibril();
582 return 3;
583 }
584}
585
586
587/**
588 * @}
589 */
Note: See TracBrowser for help on using the repository browser.