source: mainline/uspace/app/rcutest/rcutest.c@ 6ba36a0

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 6ba36a0 was 09ab0a9a, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix vertical spacing with new Ccheck revision.

  • Property mode set to 100644
File size: 19.1 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 rcutest
30 * @{
31 */
32
33/**
34 * @file rcutest.c
35 */
36
37#include <stdatomic.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <stdint.h>
41#include <str_error.h>
42#include <mem.h>
43#include <errno.h>
44#include <assert.h>
45#include <async.h>
46#include <fibril.h>
47#include <fibril_synch.h>
48#include <barrier.h>
49#include <str.h>
50
51#include <rcu.h>
52
53#define USECS_PER_SEC (1000 * 1000)
54#define USECS_PER_MS 1000
55
56/* fwd decl. */
57struct test_info;
58
59typedef struct test_desc {
60 /* Aggregate test that runs other tests already in the table test_desc. */
61 bool aggregate;
62 enum {
63 T_OTHER,
64 T_SANITY,
65 T_STRESS
66 } type;
67 bool (*func)(struct test_info *);
68 const char *name;
69 const char *desc;
70} test_desc_t;
71
72typedef struct test_info {
73 size_t thread_cnt;
74 test_desc_t *desc;
75} test_info_t;
76
77static bool run_all_tests(struct test_info *);
78static bool run_sanity_tests(struct test_info *);
79static bool run_stress_tests(struct test_info *);
80
81static bool wait_for_one_reader(struct test_info *);
82static bool basic_sanity_check(struct test_info *);
83static bool dont_wait_for_new_reader(struct test_info *);
84static bool wait_for_exiting_reader(struct test_info *);
85static bool seq_test(struct test_info *);
86
87static test_desc_t test_desc[] = {
88 {
89 .aggregate = true,
90 .type = T_OTHER,
91 .func = run_all_tests,
92 .name = "*",
93 .desc = "Runs all tests.",
94 },
95 {
96 .aggregate = true,
97 .type = T_SANITY,
98 .func = run_sanity_tests,
99 .name = "sanity-tests",
100 .desc = "Runs all RCU sanity tests.",
101 },
102 {
103 .aggregate = true,
104 .type = T_STRESS,
105 .func = run_stress_tests,
106 .name = "stress-tests",
107 .desc = "Runs all RCU stress tests.",
108 },
109
110 {
111 .aggregate = false,
112 .type = T_SANITY,
113 .func = basic_sanity_check,
114 .name = "basic-sanity",
115 .desc = "Locks/unlocks and syncs in 1 fibril, no contention.",
116 },
117 {
118 .aggregate = false,
119 .type = T_SANITY,
120 .func = wait_for_one_reader,
121 .name = "wait-for-one",
122 .desc = "Syncs with one 2 secs sleeping reader.",
123 },
124 {
125 .aggregate = false,
126 .type = T_SANITY,
127 .func = dont_wait_for_new_reader,
128 .name = "ignore-new-r",
129 .desc = "Syncs with preexisting reader; ignores new reader.",
130 },
131 {
132 .aggregate = false,
133 .type = T_SANITY,
134 .func = wait_for_exiting_reader,
135 .name = "dereg-unlocks",
136 .desc = "Lets deregister_fibril unlock the reader section.",
137 },
138 {
139 .aggregate = false,
140 .type = T_STRESS,
141 .func = seq_test,
142 .name = "seq",
143 .desc = "Checks lock/unlock/sync w/ global time sequence.",
144 },
145 {
146 .aggregate = false,
147 .type = T_OTHER,
148 .func = NULL,
149 .name = "(null)",
150 .desc = "",
151 },
152};
153
154static const size_t test_desc_cnt = sizeof(test_desc) / sizeof(test_desc[0]);
155
156/*--------------------------------------------------------------------*/
157
158static size_t next_rand(size_t seed)
159{
160 return (seed * 1103515245 + 12345) & ((1U << 31) - 1);
161}
162
163typedef errno_t (*fibril_func_t)(void *);
164
165static bool create_fibril(errno_t (*func)(void *), void *arg)
166{
167 fid_t fid = fibril_create(func, arg);
168
169 if (0 == fid) {
170 printf("Failed to create a fibril!\n");
171 return false;
172 }
173
174 fibril_add_ready(fid);
175 return true;
176}
177
178/*--------------------------------------------------------------------*/
179
180static bool run_tests(test_info_t *info, bool (*include_filter)(test_desc_t *))
181{
182 size_t failed_cnt = 0;
183 size_t ok_cnt = 0;
184
185 for (size_t i = 0; i < test_desc_cnt; ++i) {
186 test_desc_t *t = &test_desc[i];
187
188 if (t->func && !t->aggregate && include_filter(t)) {
189 printf("Running \'%s\'...\n", t->name);
190 bool ok = test_desc[i].func(info);
191
192 if (ok) {
193 ++ok_cnt;
194 printf("Passed: \'%s\'\n", t->name);
195 } else {
196 ++failed_cnt;
197 printf("FAILED: \'%s\'\n", t->name);
198 }
199 }
200 }
201
202 printf("\n");
203
204 printf("%zu tests passed\n", ok_cnt);
205
206 if (failed_cnt) {
207 printf("%zu tests failed\n", failed_cnt);
208 }
209
210 return 0 == failed_cnt;
211}
212
213/*--------------------------------------------------------------------*/
214
215static bool all_tests_include_filter(test_desc_t *desc)
216{
217 return true;
218}
219
220/* Runs all available tests tests one-by-one. */
221static bool run_all_tests(test_info_t *test_info)
222{
223 printf("Running all tests...\n");
224 return run_tests(test_info, all_tests_include_filter);
225}
226
227/*--------------------------------------------------------------------*/
228
229static bool stress_tests_include_filter(test_desc_t *desc)
230{
231 return desc->type == T_STRESS;
232}
233
234/* Runs all available stress tests one-by-one. */
235static bool run_stress_tests(test_info_t *test_info)
236{
237 printf("Running stress tests...\n");
238 return run_tests(test_info, stress_tests_include_filter);
239}
240
241/*--------------------------------------------------------------------*/
242
243static bool sanity_tests_include_filter(test_desc_t *desc)
244{
245 return desc->type == T_SANITY;
246}
247
248/* Runs all available sanity tests one-by-one. */
249static bool run_sanity_tests(test_info_t *test_info)
250{
251 printf("Running sanity tests...\n");
252 return run_tests(test_info, sanity_tests_include_filter);
253}
254
255/*--------------------------------------------------------------------*/
256
257/* Locks/unlocks rcu and synchronizes without contention in a single fibril. */
258static bool basic_sanity_check(test_info_t *test_info)
259{
260 rcu_read_lock();
261 /* nop */
262 rcu_read_unlock();
263
264 rcu_read_lock();
265 /* nop */
266 rcu_read_unlock();
267
268 rcu_synchronize();
269
270 /* Nested lock with yield(). */
271 rcu_read_lock();
272 fibril_yield();
273 rcu_read_lock();
274 fibril_yield();
275 rcu_read_unlock();
276 fibril_yield();
277 rcu_read_unlock();
278
279 fibril_yield();
280 rcu_synchronize();
281 rcu_synchronize();
282
283 rcu_read_lock();
284 /* nop */
285 if (!rcu_read_locked())
286 return false;
287
288 rcu_read_unlock();
289
290 return !rcu_read_locked();
291}
292
293typedef struct one_reader_info {
294 bool entered_cs;
295 bool exited_cs;
296 size_t done_sleeps_cnt;
297 bool synching;
298 bool synched;
299 size_t failed;
300} one_reader_info_t;
301
302static errno_t sleeping_reader(one_reader_info_t *arg)
303{
304 rcu_register_fibril();
305
306 printf("lock{");
307 rcu_read_lock();
308 rcu_read_lock();
309 arg->entered_cs = true;
310 rcu_read_unlock();
311
312 printf("r-sleep{");
313 /* 2 sec */
314 fibril_usleep(2 * USECS_PER_SEC);
315 ++arg->done_sleeps_cnt;
316 printf("}");
317
318 if (arg->synched) {
319 arg->failed = 1;
320 printf("Error: rcu_sync exited prematurely.\n");
321 }
322
323 arg->exited_cs = true;
324 rcu_read_unlock();
325 printf("}");
326
327 rcu_deregister_fibril();
328 return 0;
329}
330
331static bool wait_for_one_reader(test_info_t *test_info)
332{
333 one_reader_info_t info = { 0 };
334
335 if (!create_fibril((fibril_func_t) sleeping_reader, &info))
336 return false;
337
338 /* 1 sec, waits for the reader to enter its critical section and sleep. */
339 fibril_usleep(1 * USECS_PER_SEC);
340
341 if (!info.entered_cs || info.exited_cs) {
342 printf("Error: reader is unexpectedly outside of critical section.\n");
343 return false;
344 }
345
346 info.synching = true;
347 printf("sync[");
348 rcu_synchronize();
349 printf("]\n");
350 info.synched = true;
351
352 /* Load info.exited_cs */
353 memory_barrier();
354
355 if (!info.exited_cs || info.failed) {
356 printf("Error: rcu_sync() returned before the reader exited its CS.\n");
357 /*
358 * Sleep some more so we don't free info on stack while the reader
359 * is using it.
360 */
361 /* 1.5 sec */
362 fibril_usleep(1500 * 1000);
363 return false;
364 } else {
365 return true;
366 }
367}
368
369/*--------------------------------------------------------------------*/
370
371#define WAIT_STEP_US 500 * USECS_PER_MS
372
373typedef struct two_reader_info {
374 bool new_entered_cs;
375 bool new_exited_cs;
376 bool old_entered_cs;
377 bool old_exited_cs;
378 bool synching;
379 bool synched;
380 size_t failed;
381} two_reader_info_t;
382
383static errno_t preexisting_reader(two_reader_info_t *arg)
384{
385 rcu_register_fibril();
386
387 printf("old-lock{");
388 rcu_read_lock();
389 arg->old_entered_cs = true;
390
391 printf("wait-for-sync{");
392 /* Wait for rcu_sync() to start waiting for us. */
393 while (!arg->synching) {
394 fibril_usleep(WAIT_STEP_US);
395 }
396 printf(" }");
397
398 /* A new reader starts while rcu_sync() is in progress. */
399
400 printf("wait-for-new-R{");
401 /* Wait for the new reader to enter its reader section. */
402 while (!arg->new_entered_cs) {
403 fibril_usleep(WAIT_STEP_US);
404 }
405 printf(" }");
406
407 arg->old_exited_cs = true;
408
409 assert(!arg->new_exited_cs);
410
411 if (arg->synched) {
412 arg->failed = 1;
413 printf("Error: rcu_sync() did not wait for preexisting reader.\n");
414 }
415
416 rcu_read_unlock();
417 printf(" }");
418
419 rcu_deregister_fibril();
420 return 0;
421}
422
423static errno_t new_reader(two_reader_info_t *arg)
424{
425 rcu_register_fibril();
426
427 /* Wait until rcu_sync() starts. */
428 while (!arg->synching) {
429 fibril_usleep(WAIT_STEP_US);
430 }
431
432 /*
433 * synching is set when rcu_sync() is about to be entered so wait
434 * some more to make sure it really does start executing.
435 */
436 fibril_usleep(WAIT_STEP_US);
437
438 printf("new-lock(");
439 rcu_read_lock();
440 arg->new_entered_cs = true;
441
442 /* Wait for rcu_sync() exit, ie stop waiting for the preexisting reader. */
443 while (!arg->synched) {
444 fibril_usleep(WAIT_STEP_US);
445 }
446
447 arg->new_exited_cs = true;
448 /* Write new_exited_cs before exiting reader section. */
449 memory_barrier();
450
451 /*
452 * Preexisting reader should have exited by now, so rcu_synchronize()
453 * must have returned.
454 */
455 if (!arg->old_exited_cs) {
456 arg->failed = 1;
457 printf("Error: preexisting reader should have exited by now!\n");
458 }
459
460 rcu_read_unlock();
461 printf(")");
462
463 rcu_deregister_fibril();
464 return 0;
465}
466
467static bool dont_wait_for_new_reader(test_info_t *test_info)
468{
469 two_reader_info_t info = { 0 };
470
471 if (!create_fibril((fibril_func_t) preexisting_reader, &info))
472 return false;
473
474 if (!create_fibril((fibril_func_t) new_reader, &info))
475 return false;
476
477 /* Waits for the preexisting_reader to enter its CS.*/
478 while (!info.old_entered_cs) {
479 fibril_usleep(WAIT_STEP_US);
480 }
481
482 assert(!info.old_exited_cs);
483 assert(!info.new_entered_cs);
484 assert(!info.new_exited_cs);
485
486 printf("sync[");
487 info.synching = true;
488 rcu_synchronize();
489 printf(" ]");
490
491 /* Load info.exited_cs */
492 memory_barrier();
493
494 if (!info.old_exited_cs) {
495 printf("Error: rcu_sync() returned before preexisting reader exited.\n");
496 info.failed = 1;
497 }
498
499 bool new_outside_cs = !info.new_entered_cs || info.new_exited_cs;
500
501 /* Test if new reader is waiting in CS before setting synched. */
502 compiler_barrier();
503 info.synched = true;
504
505 if (new_outside_cs) {
506 printf("Error: new reader CS held up rcu_sync(). (4)\n");
507 info.failed = 1;
508 } else {
509 /* Wait for the new reader. */
510 rcu_synchronize();
511
512 if (!info.new_exited_cs) {
513 printf("Error: 2nd rcu_sync() returned before new reader exited.\n");
514 info.failed = 1;
515 }
516
517 printf("\n");
518 }
519
520 if (info.failed) {
521 /*
522 * Sleep some more so we don't free info on stack while readers
523 * are using it.
524 */
525 fibril_usleep(WAIT_STEP_US);
526 }
527
528 return 0 == info.failed;
529}
530
531#undef WAIT_STEP_US
532
533/*--------------------------------------------------------------------*/
534#define WAIT_STEP_US 500 * USECS_PER_MS
535
536typedef struct exit_reader_info {
537 bool entered_cs;
538 bool exited_cs;
539 bool synching;
540 bool synched;
541} exit_reader_info_t;
542
543static errno_t exiting_locked_reader(exit_reader_info_t *arg)
544{
545 rcu_register_fibril();
546
547 printf("old-lock{");
548 rcu_read_lock();
549 rcu_read_lock();
550 rcu_read_lock();
551 arg->entered_cs = true;
552
553 printf("wait-for-sync{");
554 /* Wait for rcu_sync() to start waiting for us. */
555 while (!arg->synching) {
556 fibril_usleep(WAIT_STEP_US);
557 }
558 printf(" }");
559
560 rcu_read_unlock();
561 printf(" }");
562
563 arg->exited_cs = true;
564 /* Store exited_cs before unlocking reader section in deregister. */
565 memory_barrier();
566
567 /* Deregister forcefully unlocks the reader section. */
568 rcu_deregister_fibril();
569 return 0;
570}
571
572static bool wait_for_exiting_reader(test_info_t *test_info)
573{
574 exit_reader_info_t info = { 0 };
575
576 if (!create_fibril((fibril_func_t) exiting_locked_reader, &info))
577 return false;
578
579 /* Waits for the preexisting_reader to enter its CS.*/
580 while (!info.entered_cs) {
581 fibril_usleep(WAIT_STEP_US);
582 }
583
584 assert(!info.exited_cs);
585
586 printf("sync[");
587 info.synching = true;
588 rcu_synchronize();
589 info.synched = true;
590 printf(" ]\n");
591
592 /* Load info.exited_cs */
593 memory_barrier();
594
595 if (!info.exited_cs) {
596 printf("Error: rcu_deregister_fibril did not unlock the CS.\n");
597 return false;
598 }
599
600 return true;
601}
602
603#undef WAIT_STEP_US
604
605/*--------------------------------------------------------------------*/
606
607typedef struct {
608 atomic_size_t time;
609 atomic_size_t max_start_time_of_done_sync;
610
611 size_t total_workers;
612 size_t done_reader_cnt;
613 size_t done_updater_cnt;
614 fibril_mutex_t done_cnt_mtx;
615 fibril_condvar_t done_cnt_changed;
616
617 size_t read_iters;
618 size_t upd_iters;
619
620 atomic_size_t seed;
621 int failed;
622} seq_test_info_t;
623
624static void signal_seq_fibril_done(seq_test_info_t *arg, size_t *cnt)
625{
626 fibril_mutex_lock(&arg->done_cnt_mtx);
627 ++*cnt;
628
629 if (arg->total_workers == arg->done_reader_cnt + arg->done_updater_cnt) {
630 fibril_condvar_signal(&arg->done_cnt_changed);
631 }
632
633 fibril_mutex_unlock(&arg->done_cnt_mtx);
634}
635
636static errno_t seq_reader(seq_test_info_t *arg)
637{
638 rcu_register_fibril();
639
640 size_t seed = atomic_fetch_add(&arg->seed, 1);
641 bool first = (seed == 0);
642
643 for (size_t k = 0; k < arg->read_iters; ++k) {
644 /* Print progress if the first reader fibril. */
645 if (first && 0 == k % (arg->read_iters / 100 + 1)) {
646 printf(".");
647 }
648
649 rcu_read_lock();
650 size_t start_time = atomic_fetch_add(&arg->time, 1);
651
652 /* Do some work. */
653 seed = next_rand(seed);
654 size_t idle_iters = seed % 8;
655
656 for (size_t i = 0; i < idle_iters; ++i) {
657 fibril_yield();
658 }
659
660 /*
661 * Check if the most recently started rcu_sync of the already
662 * finished rcu_syncs did not happen to start after this reader
663 * and, therefore, should have waited for this reader to exit
664 * (but did not - since it already announced it completed).
665 */
666 if (start_time <= atomic_load(&arg->max_start_time_of_done_sync)) {
667 arg->failed = 1;
668 }
669
670 rcu_read_unlock();
671 }
672
673 rcu_deregister_fibril();
674
675 signal_seq_fibril_done(arg, &arg->done_reader_cnt);
676 return 0;
677}
678
679static errno_t seq_updater(seq_test_info_t *arg)
680{
681 rcu_register_fibril();
682
683 for (size_t k = 0; k < arg->upd_iters; ++k) {
684 size_t start_time = atomic_load(&arg->time);
685 rcu_synchronize();
686
687 /* This is prone to a race but if it happens it errs to the safe side.*/
688 if (atomic_load(&arg->max_start_time_of_done_sync) < start_time) {
689 atomic_store(&arg->max_start_time_of_done_sync, start_time);
690 }
691 }
692
693 rcu_deregister_fibril();
694
695 signal_seq_fibril_done(arg, &arg->done_updater_cnt);
696 return 0;
697}
698
699static bool seq_test(test_info_t *test_info)
700{
701 size_t reader_cnt = test_info->thread_cnt;
702 size_t updater_cnt = test_info->thread_cnt;
703
704 seq_test_info_t info = {
705 .time = 0,
706 .max_start_time_of_done_sync = 0,
707 .read_iters = 10 * 1000,
708 .upd_iters = 5 * 1000,
709 .total_workers = updater_cnt + reader_cnt,
710 .done_reader_cnt = 0,
711 .done_updater_cnt = 0,
712 .done_cnt_mtx = FIBRIL_MUTEX_INITIALIZER(info.done_cnt_mtx),
713 .done_cnt_changed = FIBRIL_CONDVAR_INITIALIZER(info.done_cnt_changed),
714 .seed = 0,
715 .failed = 0,
716 };
717
718 /* Create and start worker fibrils. */
719 for (size_t k = 0; k + k < reader_cnt + updater_cnt; ++k) {
720 bool ok = create_fibril((fibril_func_t) seq_reader, &info);
721 ok = ok && create_fibril((fibril_func_t) seq_updater, &info);
722
723 if (!ok) {
724 /* Let the already created fibrils corrupt the stack. */
725 return false;
726 }
727 }
728
729 /* Wait for all worker fibrils to complete their work. */
730 fibril_mutex_lock(&info.done_cnt_mtx);
731
732 while (info.total_workers != info.done_reader_cnt + info.done_updater_cnt) {
733 fibril_condvar_wait(&info.done_cnt_changed, &info.done_cnt_mtx);
734 }
735
736 fibril_mutex_unlock(&info.done_cnt_mtx);
737
738 if (info.failed) {
739 printf("Error: rcu_sync() did not wait for a preexisting reader.");
740 }
741
742 return 0 == info.failed;
743}
744
745/*--------------------------------------------------------------------*/
746
747static bool create_threads(size_t cnt)
748{
749 /* Sanity check. */
750 assert(cnt < 1024);
751 fibril_test_spawn_runners(cnt);
752 return true;
753}
754
755/*--------------------------------------------------------------------*/
756static test_desc_t *find_test(const char *name)
757{
758 /* First match for test name. */
759 for (size_t k = 0; k < test_desc_cnt; ++k) {
760 test_desc_t *t = &test_desc[k];
761
762 if (t->func && 0 == str_cmp(t->name, name))
763 return t;
764 }
765
766 /* Try to match the test number. */
767 uint32_t test_num = 0;
768
769 if (EOK == str_uint32_t(name, NULL, 0, true, &test_num)) {
770 if (test_num < test_desc_cnt && test_desc[test_num].func) {
771 return &test_desc[test_num];
772 }
773 }
774
775 return NULL;
776}
777
778static void list_tests(void)
779{
780 printf("Available tests: \n");
781
782 for (size_t i = 0; i < test_desc_cnt; ++i) {
783 test_desc_t *t = &test_desc[i];
784
785 if (!t->func)
786 continue;
787
788 const char *type = "";
789
790 if (t->type == T_SANITY)
791 type = " (sanity)";
792 if (t->type == T_STRESS)
793 type = " (stress)";
794
795 printf("%zu: %s ..%s %s\n", i, t->name, type, t->desc);
796 }
797}
798
799static void print_usage(void)
800{
801 printf("Usage: rcutest [test_name|test_number] {number_of_threads}\n");
802 list_tests();
803
804 printf("\nExample usage:\n");
805 printf("\trcutest *\n");
806 printf("\trcutest sanity-tests\n");
807}
808
809static bool parse_cmd_line(int argc, char **argv, test_info_t *info)
810{
811 if (argc != 2 && argc != 3) {
812 print_usage();
813 return false;
814 }
815
816 info->desc = find_test(argv[1]);
817
818 if (!info->desc) {
819 printf("Non-existent test '%s'.\n", argv[1]);
820 list_tests();
821 return false;
822 }
823
824 if (argc == 3) {
825 uint32_t thread_cnt = 0;
826 errno_t ret = str_uint32_t(argv[2], NULL, 0, true, &thread_cnt);
827
828 if (ret == EOK && 1 <= thread_cnt && thread_cnt <= 64) {
829 info->thread_cnt = thread_cnt;
830 } else {
831 info->thread_cnt = 1;
832 printf("Err: Invalid number of threads '%s'; using 1.\n", argv[2]);
833 }
834 } else {
835 info->thread_cnt = 1;
836 }
837
838 return true;
839}
840
841int main(int argc, char **argv)
842{
843 rcu_register_fibril();
844
845 test_info_t info;
846
847 bool ok = parse_cmd_line(argc, argv, &info);
848 ok = ok && create_threads(info.thread_cnt - 1);
849
850 if (ok) {
851 assert(1 <= info.thread_cnt);
852 test_desc_t *t = info.desc;
853
854 printf("Running '%s' (in %zu threads)...\n", t->name, info.thread_cnt);
855 ok = t->func(&info);
856
857 printf("%s: '%s'\n", ok ? "Passed" : "FAILED", t->name);
858
859 rcu_deregister_fibril();
860
861 /* Let the kernel clean up the created background threads. */
862 return ok ? 0 : 1;
863 } else {
864 rcu_deregister_fibril();
865 return 2;
866 }
867}
868
869/**
870 * @}
871 */
Note: See TracBrowser for help on using the repository browser.