// License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2021 Intel Corporation. All Rights Reserved. #include #include #include #include #include #include using namespace rsutils::time; // We use this function as a CPU stress test function int fibo( int num ) { if( num < 2 ) return 1; return fibo( num - 1 ) + fibo( num - 2 ); } TEST_CASE( "dispatcher main flow" ) { dispatcher d(3); std::atomic_bool run = { false }; auto func = [&](dispatcher::cancellable_timer c) { c.try_sleep(std::chrono::seconds(1)); run = true; }; d.start(); REQUIRE(d.empty()); // We want to make sure that if we invoke some functions, the dispatcher is not empty. // We add 2 functions that take some time so that even if the first one pop, // the second will still be in the queue and it will not be empty d.invoke(func); d.invoke(func); REQUIRE_FALSE(d.empty()); REQUIRE(d.flush()); REQUIRE(run); d.stop(); } TEST_CASE( "invoke and wait" ) { dispatcher d(2); std::atomic_bool run = { false }; auto func = [&](dispatcher::cancellable_timer c) { std::this_thread::sleep_for(std::chrono::seconds(3)); run = true; }; d.start(); stopwatch sw; d.invoke_and_wait(func, []() {return false; }, true); REQUIRE( sw.get_elapsed() > std::chrono::seconds( 3 ) ); // verify we get here only after the function call ended d.stop(); } TEST_CASE("verify stop() not consuming high CPU usage") { // using shared_ptr because no copy constructor is allowed for a dispatcher. std::vector> dispatchers; for (int i = 0 ; i < 32; ++i) { dispatchers.push_back(std::make_shared(10)); } for (auto &&dispatcher : dispatchers) { dispatcher->start(); } for (auto&& dispatcher : dispatchers) { dispatcher->stop(); } // Allow some time for all threads to do some work std::this_thread::sleep_for(std::chrono::seconds(5)); stopwatch sw; // Do some stress work REQUIRE(fibo(40) == 165580141); // Verify the stress test did not take too long. // We had an issue that stop() call cause a high CPU usage and therefore other operations stall, // This test took > 9 seconds on an 8 cores PC, after the fix it took ~1.5 sec on 1 core run (on release configuration). // We allow 9 seconds to support debug configuration as well REQUIRE( sw.get_elapsed() < std::chrono::seconds( 9 ) ); } TEST_CASE("stop() notify flush to finish") { // On this test we check that if during a flush() another thread call stop(), // than the flush CV will be triggered to exit and not wait a full timeout dispatcher dispatcher( 10 ); dispatcher.start(); stopwatch sw; std::atomic_bool dispatched_end_verifier{ false }; dispatcher.invoke( [&]( dispatcher::cancellable_timer c ) { //std::cout << "Sleeping from inside invoke" << std::endl; std::this_thread::sleep_for( std::chrono::seconds( 3 ) ); //std::cout << "Sleeping from inside invoke - Done" << std::endl; dispatched_end_verifier = true; } ); // Make sure the above invoke function is dispatched std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); std::thread stop_thread( [&]() { // Make sure we postpone the stop to after the flush call std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); //std::cout << "Stopping dispatcher" << std::endl; dispatcher.stop(); } ); auto timeout = std::chrono::seconds(5); //std::cout << "Flushing dispatcher" << std::endl; CHECK(!dispatcher.flush(timeout)); // We expect that flush will be triggered by stop only after the dispatched function end. CHECK(dispatched_end_verifier); //std::cout << "Flushing is done" << std::endl; CHECK( sw.get_elapsed() < timeout ); stop_thread.join(); }