From 9e5d479d03793a88098541f08dbf9dfc87ddbfe8 Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Thu, 10 Sep 2020 15:29:34 +0200 Subject: [PATCH] timeout: test smp support Ref #3884 --- repos/base/run/timeout_smp.run | 40 +++ repos/base/src/test/timeout_smp/main.cc | 412 ++++++++++++++++++++++ repos/base/src/test/timeout_smp/target.mk | 3 + tool/autopilot.list | 1 + 4 files changed, 456 insertions(+) create mode 100644 repos/base/run/timeout_smp.run create mode 100644 repos/base/src/test/timeout_smp/main.cc create mode 100644 repos/base/src/test/timeout_smp/target.mk diff --git a/repos/base/run/timeout_smp.run b/repos/base/run/timeout_smp.run new file mode 100644 index 0000000000..826e8aca74 --- /dev/null +++ b/repos/base/run/timeout_smp.run @@ -0,0 +1,40 @@ +build { core init timer test/timeout_smp } + +create_boot_directory + +install_config { + + + + + + + + + + + + + + + + + + + + + + + + + + +} +build_boot_image { core ld.lib.so init timer test-timeout_smp } + +append qemu_args " -nographic" + +run_genode_until "child \"test\" exited with exit value.*\n" 60 +grep_output {\[init\] child "test" exited with exit value} +compare_output_to {[init] child "test" exited with exit value 0} + diff --git a/repos/base/src/test/timeout_smp/main.cc b/repos/base/src/test/timeout_smp/main.cc new file mode 100644 index 0000000000..70d7a1dfea --- /dev/null +++ b/repos/base/src/test/timeout_smp/main.cc @@ -0,0 +1,412 @@ +/* + * \brief Test multiprocessor support of Timeout framework + * \author Martin Stein + * \date 2020-09-10 + */ + +/* + * Copyright (C) 2020 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License ve sion 3. + */ + +/* Genode includes */ +#include +#include + +using namespace Genode; + +enum { MIN_NR_OF_TEST_ITERATIONS = 10 }; + +template +class Test_thread +: + public Thread, + public Noncopyable +{ + private: + + enum { STACK_SIZE = sizeof(unsigned long) * 4096 }; + + typedef void (TYPE::*Method)(); + + TYPE &_object; + Method const _method; + + void entry() override { (_object.*_method)(); } + + public: + + Test_thread( + Env &env, + TYPE &object, + Method method, + unsigned long cpu_idx, + Affinity::Space affinity_space) + : + Thread { + env, + Name("test_thread"), + STACK_SIZE, + affinity_space.location_of_index( + cpu_idx % affinity_space.total()), + Weight(), + env.cpu() + }, + _object { object }, + _method { method } + { } +}; + +template +class Test_smp_2 +{ + private: + + using Test_timeout = Timer::One_shot_timeout; + + Env &_env; + unsigned long &_nr_of_errors; + Timer::Connection _timeout_timer { _env }; + Timer::Connection _sleep_timer { _env }; + Test_timeout _timeout_1 { _timeout_timer, *this, &Test_smp_2::_handle_timeout_1 }; + Test_timeout _timeout_2 { _timeout_timer, *this, &Test_smp_2::_handle_timeout_2 }; + Test_timeout _timeout_3 { _timeout_timer, *this, &Test_smp_2::_handle_timeout_3 }; + Test_timeout _timeout_4 { _timeout_timer, *this, &Test_smp_2::_handle_timeout_4 }; + Test_timeout _timeout_5 { _timeout_timer, *this, &Test_smp_2::_handle_timeout_5 }; + unsigned long _count_1 { 0 }; + unsigned long _count_2 { 0 }; + unsigned long _count_3 { 0 }; + unsigned long _count_4 { 0 }; + unsigned long _count_5 { 0 }; + unsigned long _cpu_idx { 1 }; + bool volatile _timeouts_discarded { false }; + bool _done_called { false }; + Mutex _done_mutex { }; + Signal_transmitter _done_transmitter; + Affinity::Space _affinity_space { _env.cpu().affinity_space() }; + + Test_thread _discard_timeouts_thread + { + _env, *this, &Test_smp_2::discard_timeouts_thread_entry, + _cpu_idx++, _affinity_space + }; + + void inline _handle_timeout(Test_timeout &timeout, + unsigned long &count) + { + if (_timeouts_discarded) { + Mutex::Guard guard { _done_mutex }; + log(" Timeout handler called after timeouts were discarded"); + _done(false); + } else { + count++; + timeout.schedule(Microseconds(1)); + } + } + + void _handle_timeout_1(Duration) { _handle_timeout(_timeout_1, _count_1); } + void _handle_timeout_2(Duration) { _handle_timeout(_timeout_2, _count_2); } + void _handle_timeout_3(Duration) { _handle_timeout(_timeout_3, _count_3); } + void _handle_timeout_4(Duration) { _handle_timeout(_timeout_4, _count_4); } + void _handle_timeout_5(Duration) { _handle_timeout(_timeout_5, _count_5); } + + void _done(bool success) + { + if (_done_called) { + return; + } + _done_called = true; + + if (NR_OF_TIMEOUTS >= 1 && _count_1 < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout 1 has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (NR_OF_TIMEOUTS >= 2 && _count_2 < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout 2 has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (NR_OF_TIMEOUTS >= 3 && _count_3 < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout 3 has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (NR_OF_TIMEOUTS >= 4 && _count_4 < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout 4 has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (NR_OF_TIMEOUTS >= 5 && _count_5 < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout 5 has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (success) { + log(" Succeeded"); + } else { + log(" Failed"); + _nr_of_errors++; + } + if (NR_OF_TIMEOUTS >= 1) { log(" Timeout 1 handled: ", _count_1, " times"); } + if (NR_OF_TIMEOUTS >= 2) { log(" Timeout 2 handled: ", _count_2, " times"); } + if (NR_OF_TIMEOUTS >= 3) { log(" Timeout 3 handled: ", _count_3, " times"); } + if (NR_OF_TIMEOUTS >= 4) { log(" Timeout 4 handled: ", _count_4, " times"); } + if (NR_OF_TIMEOUTS >= 5) { log(" Timeout 5 handled: ", _count_5, " times"); } + _done_transmitter.submit(); + } + + public: + + Test_smp_2( + Env &env, + unsigned long &nr_of_errors, + Signal_context_capability done_sigh, + unsigned long test_idx) + : + _env { env }, + _nr_of_errors { nr_of_errors }, + _done_transmitter { done_sigh } + { + log("Start test ", test_idx); + _discard_timeouts_thread.start(); + if (NR_OF_TIMEOUTS >= 1) { _timeout_1.schedule(Microseconds(1)); } + if (NR_OF_TIMEOUTS >= 2) { _timeout_2.schedule(Microseconds(1)); } + if (NR_OF_TIMEOUTS >= 3) { _timeout_3.schedule(Microseconds(1)); } + if (NR_OF_TIMEOUTS >= 4) { _timeout_4.schedule(Microseconds(1)); } + if (NR_OF_TIMEOUTS >= 5) { _timeout_5.schedule(Microseconds(1)); } + } + + ~Test_smp_2() + { + _discard_timeouts_thread.join(); + } + + void discard_timeouts_thread_entry() + { + Timer::Connection sleep_timer { _env }; + sleep_timer.msleep(500); + if (NR_OF_TIMEOUTS >= 1) { _timeout_1.discard(); } + if (NR_OF_TIMEOUTS >= 2) { _timeout_2.discard(); } + if (NR_OF_TIMEOUTS >= 3) { _timeout_3.discard(); } + if (NR_OF_TIMEOUTS >= 4) { _timeout_4.discard(); } + if (NR_OF_TIMEOUTS >= 5) { _timeout_5.discard(); } + _timeouts_discarded = true; + sleep_timer.msleep(500); + + Mutex::Guard guard { _done_mutex }; + _done(true); + } +}; + + +class Test_smp_1 +{ + private: + + using Test_timeout = + Constructible >; + + Env &_env; + unsigned long &_nr_of_errors; + unsigned long _cpu_idx { 1 }; + bool _max_nr_of_handle_calls_reached { false }; + Timer::Connection _timeout_timer { _env }; + Timer::Connection _sleep_timer { _env }; + Timer::Connection _cancel_test_thread_timer { _env }; + Test_timeout _timeout { }; + bool _done_called { false }; + Mutex _done_mutex { }; + Signal_transmitter _done_transmitter; + Affinity::Space _affinity_space { _env.cpu().affinity_space() }; + unsigned long _nr_of_handle_calls { 0 }; + unsigned long _nr_of_discard_calls { 0 }; + unsigned long _nr_of_destruct_calls { 0 }; + + Test_thread _destruct_discard_timeout_thread + { + _env, *this, &Test_smp_1::destruct_discard_timeout_thread_entry, + _cpu_idx++, _affinity_space + }; + + Test_thread _cancel_test_thread + { + _env, *this, &Test_smp_1::cancel_test_thread_entry, _cpu_idx++, + _affinity_space + }; + + void _handle_timeout(Duration) + { + if (_nr_of_handle_calls < 1000) { + _nr_of_handle_calls++; + _schedule_timeout(); + } else { + _max_nr_of_handle_calls_reached = true; + } + } + + void _schedule_timeout() + { + _timeout->schedule(Microseconds(333)); + } + + void _construct_timeout() + { + _timeout.construct( + _timeout_timer, + *this, + &Test_smp_1::_handle_timeout + ); + _schedule_timeout(); + } + + void _done(bool success) + { + if (_done_called) { + return; + } + _done_called = true; + + if (_nr_of_handle_calls < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout has to be handled at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (_nr_of_discard_calls < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout has to be discarded at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (_nr_of_destruct_calls < MIN_NR_OF_TEST_ITERATIONS) { + log(" Timeout has to be destructed at least ", + (unsigned)MIN_NR_OF_TEST_ITERATIONS, " times"); + success = false; + } + if (success) { + log(" Succeeded"); + } else { + log(" Failed"); + _nr_of_errors++; + } + log(" Handled: ", _nr_of_handle_calls, " times"); + log(" Discarded: ", _nr_of_discard_calls, " times"); + log(" Destructed: ", _nr_of_destruct_calls, " times"); + _done_transmitter.submit(); + } + + public: + + Test_smp_1( + Env &env, + unsigned long &nr_of_errors, + Signal_context_capability done_sigh, + unsigned long test_idx) + : + _env { env }, + _nr_of_errors { nr_of_errors }, + _done_transmitter { done_sigh } + { + log("Start test ", test_idx); + _construct_timeout(); + _cancel_test_thread.start(); + _destruct_discard_timeout_thread.start(); + } + + ~Test_smp_1() + { + _destruct_discard_timeout_thread.join(); + _cancel_test_thread.join(); + } + + void destruct_discard_timeout_thread_entry() + { + Timer::Connection sleep_timer { _env }; + while (true) { + if (_max_nr_of_handle_calls_reached) { + Mutex::Guard guard { _done_mutex }; + _done(true); + break; + } + if (_nr_of_destruct_calls < _nr_of_discard_calls) { + sleep_timer.msleep(25); + _timeout.destruct(); + sleep_timer.msleep(9); + _nr_of_destruct_calls++; + _construct_timeout(); + } else { + sleep_timer.msleep(23); + _timeout->discard(); + sleep_timer.msleep(11); + _nr_of_discard_calls++; + _schedule_timeout(); + } + } + } + + void cancel_test_thread_entry() + { + Timer::Connection sleep_timer { _env }; + for (unsigned seconds = 0; seconds < 30; seconds++) { + sleep_timer.msleep(1000); + if (_done_called) { + return; + } + } + Mutex::Guard guard { _done_mutex }; + log(" Test didn't finish in time"); + _done(false); + } +}; + +class Main +{ + private: + + Env &_env; + unsigned long _nr_of_errors { 0 }; + + Constructible > _test_0 { }; + Constructible > _test_1 { }; + Constructible > _test_2 { }; + Constructible > _test_3 { }; + Constructible > _test_4 { }; + Constructible _test_5 { }; + + Signal_handler
_test_0_done_sigh { _env.ep(), *this, &Main::_handle_test_0_done }; + Signal_handler
_test_1_done_sigh { _env.ep(), *this, &Main::_handle_test_1_done }; + Signal_handler
_test_2_done_sigh { _env.ep(), *this, &Main::_handle_test_2_done }; + Signal_handler
_test_3_done_sigh { _env.ep(), *this, &Main::_handle_test_3_done }; + Signal_handler
_test_4_done_sigh { _env.ep(), *this, &Main::_handle_test_4_done }; + Signal_handler
_test_5_done_sigh { _env.ep(), *this, &Main::_handle_test_5_done }; + + void _handle_test_0_done() { _test_0.destruct(); _test_1.construct(_env, _nr_of_errors, _test_1_done_sigh, 1); } + void _handle_test_1_done() { _test_1.destruct(); _test_2.construct(_env, _nr_of_errors, _test_2_done_sigh, 2); } + void _handle_test_2_done() { _test_2.destruct(); _test_3.construct(_env, _nr_of_errors, _test_3_done_sigh, 3); } + void _handle_test_3_done() { _test_3.destruct(); _test_4.construct(_env, _nr_of_errors, _test_4_done_sigh, 4); } + void _handle_test_4_done() { _test_4.destruct(); _test_5.construct(_env, _nr_of_errors, _test_5_done_sigh, 5); } + void _handle_test_5_done() { _test_5.destruct(); + if (_nr_of_errors > 0) { + log("Some tests failed"); + _env.parent().exit(-1); + } else { + log("All tests succeeded"); + _env.parent().exit(0); + } + } + + public: + + Main(Env &env) + : + _env { env } + { + _test_0.construct(_env, _nr_of_errors, _test_0_done_sigh, 0); + } +}; + +void Component::construct(Env & env) +{ + static Main main { env }; +} diff --git a/repos/base/src/test/timeout_smp/target.mk b/repos/base/src/test/timeout_smp/target.mk new file mode 100644 index 0000000000..e8f4eaf79e --- /dev/null +++ b/repos/base/src/test/timeout_smp/target.mk @@ -0,0 +1,3 @@ +TARGET = test-timeout_smp +SRC_CC = main.cc +LIBS = base diff --git a/tool/autopilot.list b/tool/autopilot.list index 3d5ff01d67..5e424229b2 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -59,6 +59,7 @@ sub_rm tar_rom thread timeout +timeout_smp timer_accuracy tz_vmm usb_hid