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.

133 lines
4.4 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2021 Intel Corporation. All Rights Reserved.
#include <rsutils/concurrency/concurrency.h>
#include <rsutils/easylogging/easyloggingpp.h>
#include <rsutils/time/waiting-on.h>
dispatcher::dispatcher( unsigned int cap, std::function< void( action ) > on_drop_callback )
: _queue( cap, on_drop_callback )
, _was_stopped( true )
, _is_alive( true )
{
// We keep a running thread that takes stuff off our queue and dispatches them
_thread = std::thread([&]()
{
int timeout_ms = 5000;
while( _is_alive )
{
if( _wait_for_start( timeout_ms ) )
{
std::function< void(cancellable_timer) > item;
if (_queue.dequeue(&item, timeout_ms))
{
cancellable_timer time(this);
try
{
// While we're dispatching the item, we cannot stop!
std::lock_guard< std::mutex > lock(_dispatch_mutex);
item(time);
}
catch (const std::exception& e)
{
LOG_ERROR("Dispatcher [" << this << "] exception caught: " << e.what());
}
catch (...)
{
LOG_ERROR("Dispatcher [" << this << "] unknown exception caught!");
}
}
}
}
});
}
dispatcher::~dispatcher()
{
// Don't get into any more dispatches
_is_alive = false;
// Stop whatever's in-progress, if any
stop();
// Wait until our worker thread quits
if( _thread.joinable() )
_thread.join();
}
void dispatcher::start()
{
{
std::lock_guard< std::mutex > lock(_was_stopped_mutex);
_was_stopped = false;
}
_queue.start();
// Wake up all threads that wait for the dispatcher to start
_was_stopped_cv.notify_all();
}
void dispatcher::stop()
{
// With the following commented-out if, we have issues!
// It seems stop is called multiple times and the queues are somehow waiting on something after
// the first time. If we return, those queues aren't woken! If we continue, the only effect will
// be to notify_all and we get good behavior...
//
//if( _was_stopped )
// return;
// First things first: don't accept any more incoming stuff, and get rid of anything
// pending
_queue.stop();
// Wait until any dispatched is done...
{
std::lock_guard< std::mutex > lock(_dispatch_mutex);
assert(_queue.empty());
}
// Signal we've stopped so any sleeping dispatched will wake up immediately
{
std::lock_guard< std::mutex > lock( _was_stopped_mutex );
_was_stopped = true;
}
_was_stopped_cv.notify_all();
}
// Return when all current items in the queue are finished (within a timeout).
// If additional items are added while we're waiting, those will not be waited on!
// Returns false if a timeout occurred before we were done
//
bool dispatcher::flush( std::chrono::steady_clock::duration timeout )
{
if( _was_stopped )
return true; // Nothing to do - so success (no timeout)
rsutils::time::waiting_on< bool > invoked( _was_stopped_cv, _was_stopped_mutex, false );
// Blocking call, we don't want the item in the queue to drop if the queue is full.
// TODO - Add a timeout to blocking invoke, Currently it can wait forever here.
auto invoked_in_thread = invoked.in_thread();
invoke( [invoked_in_thread]( cancellable_timer ) { invoked_in_thread.signal( true ); }, true );
invoked.wait_until( timeout, [&]() { return invoked || _was_stopped; } );
return invoked;
}
// Return true if dispatcher is started (within a timeout).
// false if not or the dispatcher is no longer alive
//
bool dispatcher::_wait_for_start( int timeout_ms )
{
// If the dispatcher is not started wait for a start event, if not such event within given timeout do nothing.
// If during the wait the thread destructor is called (_is_alive = false) do nothing as well.
std::unique_lock< std::mutex > lock(_was_stopped_mutex);
return _was_stopped_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this]() {
return !_was_stopped.load() || !_is_alive;
} ) && _is_alive;
}