You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
5.7 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2021 Intel Corporation. All Rights Reserved.
//#cmake:dependencies rsutils
#include <rsutils/string/chrono.h> // must be before catch.h!
#include <unit-tests/catch.h>
#include <rsutils/time/waiting-on.h>
#include <rsutils/time/timer.h>
#include <queue>
using rsutils::time::waiting_on;
using namespace rsutils::time;
bool invoke( size_t delay_in_thread, size_t timeout )
{
std::condition_variable cv;
std::mutex m;
waiting_on< bool > invoked( cv, m, false );
auto invoked_in_thread = invoked.in_thread();
auto lambda = [delay_in_thread, invoked_in_thread]() {
// std::cout << "In thread" << std::endl;
std::this_thread::sleep_for( std::chrono::seconds( delay_in_thread ) );
// std::cout << "Signalling" << std::endl;
invoked_in_thread.signal( true );
};
// std::cout << "Starting thread" << std::endl;
std::thread( lambda ).detach();
// std::cout << "Waiting" << std::endl;
invoked.wait_until( std::chrono::seconds( timeout ), [&]() { return invoked; } );
// std::cout << "After wait" << std::endl;
return invoked;
}
TEST_CASE( "Basic wait" )
{
REQUIRE( invoke( 1 /* seconds in thread */, 10 /* timeout */ ) );
}
TEST_CASE( "Timeout" )
{
REQUIRE( ! invoke( 10 /* seconds in thread */, 1 /* timeout */ ) );
}
TEST_CASE( "Struct usage" )
{
struct value_t
{
double d;
std::atomic_int i{ 0 };
};
std::condition_variable cv;
std::mutex m;
waiting_on< value_t > output( cv, m );
output->d = 2.;
auto output_ = output.in_thread();
std::thread( [&m, output_]() {
auto p_output = output_.still_alive();
auto & output = *p_output;
while( output->i < 30 )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
std::lock_guard< std::mutex > lock( m );
++output->i;
output.signal();
}
} ).detach();
// Within a second, i should reach ~20, but we'll ask it top stop when it reaches 10
output.wait_until( std::chrono::seconds( 1 ), [&]() { return output->i == 10; } );
auto i1 = output->i.load();
CHECK( i1 >= 10 ); // the thread is still running!
CHECK( i1 < 16 );
// std::cout << "i1= " << i1 << std::endl;
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
auto i2 = output->i.load();
CHECK( i2 > i1 ); // the thread is still running!
// std::cout << "i2= " << i2 << std::endl;
// Wait until it's done, ~30x50ms = 1.5 seconds total
REQUIRE( output->i < 30 );
output.wait_until( std::chrono::seconds( 3 ), [&]() { return false; } );
REQUIRE( output->i == 30 );
}
TEST_CASE( "Not invoked but still notified by destructor" )
{
// Emulate some dispatcher
typedef std::function< void() > func;
auto dispatcher = new std::queue< func >;
// Push some stuff onto it (not important what)
int i = 0;
dispatcher->push( [&]() { ++i; } );
dispatcher->push( [&]() { ++i; } );
// Add something we'll be waiting on
std::condition_variable cv;
std::mutex m;
rsutils::time::waiting_on< bool > invoked( cv, m, false );
dispatcher->push(
[invoked_in_thread = invoked.in_thread()]() { invoked_in_thread.signal( true ); } );
// Destroy the dispatcher while we're waiting on the invocation!
std::thread( [&]() {
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
delete dispatcher;
} ).detach();
// Wait for it -- we'd expect that, when 'invoked_in_thread' is destroyed, it'll wake us up and
// not wait for the timeout
stopwatch sw;
invoked.wait_until( std::chrono::seconds( 5 ), [&]() { return invoked; } );
auto waited = sw.get_elapsed();
REQUIRE( waited > std::chrono::milliseconds( 1990 ) );
REQUIRE( waited < std::chrono::milliseconds( 3000 ) ); // Up to a second buffer
}
TEST_CASE( "Not invoked but still notified by predicate (stopped)" )
{
// Add something we'll be waiting on
std::condition_variable cv;
std::mutex m;
rsutils::time::waiting_on< bool > invoked( cv, m, false );
// Destroy the dispatcher while we're waiting on the invocation!
std::atomic_bool stopped( false );
std::thread( [&]() {
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
std::lock_guard< std::mutex > lock( m );
stopped = true;
cv.notify_all();
} ).detach();
// When 'stopped' is turned on , it'll wake us up and not wait for the timeout
stopwatch sw;
auto wait_start = std::chrono::high_resolution_clock::now();
invoked.wait_until( std::chrono::seconds( 5 ), [&]() {
return invoked || stopped; // Without stopped, invoked will be false and we'll wait again
// even after we're signalled!
} );
auto wait_end = std::chrono::high_resolution_clock::now();
auto waited = sw.get_elapsed();
REQUIRE( waited > std::chrono::milliseconds( 1990 ) );
REQUIRE( waited < std::chrono::milliseconds( 3000 ) ); // Up to a second buffer
}
TEST_CASE( "Not invoked flush timeout expected" )
{
// Add something we'll be waiting on
std::condition_variable cv;
std::mutex m;
rsutils::time::waiting_on< bool > invoked( cv, m, false );
stopwatch sw;
auto timeout = std::chrono::seconds( 2 );
invoked.wait_until( timeout, [&]() { return invoked; } );
auto wait_time = sw.get_elapsed();
INFO( wait_time.count() );
INFO( timeout.count() );
REQUIRE((wait_time >= timeout || to_string(wait_time) == to_string(timeout))); // timeout can occur slightly before what<61>s specified (1.9999975s) but as long as it translates to <20>2s<32> we<77>re fine
}