diff --git a/CMakeLists.txt b/CMakeLists.txt index 14e7a8c..19bf7ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ daq_add_application(datahandlinglibs_test_bufferedfilewriter test_bufferedfilewr daq_add_application(datahandlinglibs_test_bufferedfilereader test_bufferedfilereader_app.cxx TEST LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) daq_add_application(datahandlinglibs_test_skiplist test_skiplist_app.cxx TEST LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) daq_add_application(datahandlinglibs_test_composite_key test_composite_key_app.cxx TEST LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) +daq_add_application(datahandlinglibs_test_co_timeout test_co_timeout_app.cxx TEST LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) ############################################################################## # Unit Tests @@ -83,6 +84,7 @@ daq_add_application(datahandlinglibs_test_composite_key test_composite_key_app.c #daq_add_unit_test(datahandlinglibs_BufferedReadWrite_test LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) #daq_add_unit_test(datahandlinglibs_VariableSizeElementQueue_test LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) daq_add_unit_test(datahandlinglibs_DataMoveCallbackRegistry_test LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) +daq_add_unit_test(datahandlinglibs_DataHandlingModel_test LINK_LIBRARIES datahandlinglibs ${BOOST_LIBS}) ############################################################################## # Installation diff --git a/include/datahandlinglibs/ReadoutTypes.hpp b/include/datahandlinglibs/ReadoutTypes.hpp index 1845f9c..bc1b085 100644 --- a/include/datahandlinglibs/ReadoutTypes.hpp +++ b/include/datahandlinglibs/ReadoutTypes.hpp @@ -8,6 +8,8 @@ #ifndef DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_READOUTTYPES_HPP_ #define DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_READOUTTYPES_HPP_ +#include "daqdataformats/FragmentHeader.hpp" + #include // uint_t types #include // unique_ptr #include // std::tie @@ -43,6 +45,12 @@ struct DUMMY_FRAME_STRUCT return timestamp; } + size_t get_num_frames() const { return frames_per_element; } + + size_t get_frame_size() const { return frame_size; } + + size_t get_payload_size() const { return get_num_frames() * get_frame_size(); } + void set_another_key(uint64_t compkey) { another_key = compkey; @@ -65,6 +73,11 @@ struct DUMMY_FRAME_STRUCT static const constexpr size_t frame_size = DUMMY_FRAME_SIZE; static const constexpr uint8_t frames_per_element = 1; // NOLINT(build/unsigned) static const constexpr size_t element_size = DUMMY_FRAME_SIZE; + static const constexpr dunedaq::daqdataformats::SourceID::Subsystem subsystem = + dunedaq::daqdataformats::SourceID::Subsystem::kUnknown; + static const constexpr dunedaq::daqdataformats::FragmentType fragment_type = + dunedaq::daqdataformats::FragmentType::kUnknown; + static const constexpr uint64_t expected_tick_difference = 1; // NOLINT(build/unsigned) }; } // namespace types diff --git a/include/datahandlinglibs/concepts/LatencyBufferConcept.hpp b/include/datahandlinglibs/concepts/LatencyBufferConcept.hpp index 3143883..b36b40f 100644 --- a/include/datahandlinglibs/concepts/LatencyBufferConcept.hpp +++ b/include/datahandlinglibs/concepts/LatencyBufferConcept.hpp @@ -13,10 +13,14 @@ #include "opmonlib/MonitorableObject.hpp" #include +#include namespace dunedaq { namespace datahandlinglibs { +template +concept SupportsDelayedPostprocessing = T::supports_delayed_postprocessing; + /** * Concept of a LatencyBuffer. * diff --git a/include/datahandlinglibs/concepts/RawDataProcessorConcept.hpp b/include/datahandlinglibs/concepts/RawDataProcessorConcept.hpp index 1ecb69c..1313057 100644 --- a/include/datahandlinglibs/concepts/RawDataProcessorConcept.hpp +++ b/include/datahandlinglibs/concepts/RawDataProcessorConcept.hpp @@ -47,6 +47,9 @@ class RawDataProcessorConcept : public opmonlib::MonitorableObject virtual void preprocess_item(ReadoutType* item) = 0; //! Postprocess one element virtual void postprocess_item(const ReadoutType* item) = 0; + //! Handle postprocess timeout event + virtual void invoke_postprocess_schedule_timeout_policy(std::uint64_t accumulated_timeout_ticks) = 0; + }; } // namespace datahandlinglibs diff --git a/include/datahandlinglibs/models/DataHandlingModel.hpp b/include/datahandlinglibs/models/DataHandlingModel.hpp index 2fb6d1a..6711390 100644 --- a/include/datahandlinglibs/models/DataHandlingModel.hpp +++ b/include/datahandlinglibs/models/DataHandlingModel.hpp @@ -47,8 +47,9 @@ #include #include -#include +#include +#include #include #include #include @@ -132,6 +133,126 @@ class DataHandlingModel : public DataHandlingConcept std::function m_consume_callback; protected: + class PostprocessScheduleAlgorithm + { + public: + PostprocessScheduleAlgorithm(LatencyBufferType& latency_buffer_impl, + RawDataProcessorType& raw_processor_impl, + uint64_t processing_delay_ticks, // NOLINT(build/unsigned) + uint64_t post_processing_delay_min_wait, // NOLINT(build/unsigned) + uint64_t post_processing_delay_max_wait) // NOLINT(build/unsigned) + : m_latency_buffer_impl{ latency_buffer_impl } + , m_raw_processor_impl{ raw_processor_impl } + , m_processing_delay_ticks{ processing_delay_ticks } + , m_post_processing_delay_min_wait{ post_processing_delay_min_wait } + , m_post_processing_delay_max_wait{ post_processing_delay_max_wait } + , m_first_cycle{ true } + , m_processed_up_to{} + , m_last_post_proc_time{ std::chrono::system_clock::now() } + , m_consecutive_timeouts{ 0 } + , m_max_wait_in_ticks{ post_processing_delay_max_wait * 62500 } // FIXME: hardcoded clock frequency + { + } + + // High-level interface + // Schedule deferred post-processing and notify timeout expiration to the processor + int run(bool timeout) { + int processed = this->do_run(timeout); + + if (timeout) { + timestamp_t timeout_accumulated = m_consecutive_timeouts * m_max_wait_in_ticks; + m_raw_processor_impl.invoke_postprocess_schedule_timeout_policy(timeout_accumulated); + } + + return processed; + } + + + // Deferral of the post processing, to allow elements being reordered in the LB + // Basically, find data older than a certain timestamp and process all data since the last post-processed element up to that value + int do_run(bool timeout) + { + if (m_latency_buffer_impl.occupancy() == 0) { + TLOG_DEBUG(TLVL_WORK_STEPS) << "Nothing to postprocess (empty buffer)"; + return 0; + } + + if (m_first_cycle) { + auto head = m_latency_buffer_impl.front(); + m_processed_up_to.set_timestamp(head->get_timestamp()); + m_first_cycle = false; + TLOG() << "***** First pass post processing *****"; + } + + // Get the LB boundaries + auto tail = m_latency_buffer_impl.back(); + auto newest_ts = tail->get_timestamp(); + + timestamp_t end_win_ts = 0; + std::chrono::time_point now{ std::chrono::system_clock::now() }; + + if (timeout) { + // Return if the last processed timestamp is greater than the newest timestamp + // This condition occurs after a timeout + if (m_processed_up_to.get_timestamp() >= newest_ts + 1) { + TLOG_DEBUG(TLVL_WORK_STEPS) << "Nothing to postprocess (at or past cap)"; + return 0; + } + + ++m_consecutive_timeouts; + timestamp_t timeout_accumulated = m_consecutive_timeouts * m_max_wait_in_ticks; + + end_win_ts = newest_ts - m_processing_delay_ticks + timeout_accumulated; + end_win_ts = std::min(end_win_ts, newest_ts + 1); // Cap to prevent end_win_ts from becoming unnecessarily large + } else { + m_consecutive_timeouts = 0; + auto milliseconds = std::chrono::duration_cast(now - m_last_post_proc_time); + + if (milliseconds.count() > m_post_processing_delay_min_wait) { + if (newest_ts - m_processed_up_to.get_timestamp() > m_processing_delay_ticks) { + end_win_ts = newest_ts - m_processing_delay_ticks; + } else { + TLOG_DEBUG(TLVL_WORK_STEPS) << "Not ready to postprocess (m_processing_delay_ticks is greater)"; + return 0; + } + } else { + TLOG_DEBUG(TLVL_WORK_STEPS) << "Not ready to postprocess (too fast)"; + return 0; + } + } + + auto start_iter = m_latency_buffer_impl.lower_bound(m_processed_up_to, false); + m_processed_up_to.set_timestamp(end_win_ts); + auto end_iter = m_latency_buffer_impl.lower_bound(m_processed_up_to, false); + + if (start_iter == end_iter) { + TLOG_DEBUG(TLVL_WORK_STEPS) << "Nothing to postprocess (start_iter == end_iter)"; + return 0; + } + + int processed = 0; + for (auto it = start_iter; it != end_iter; ++it) { + m_raw_processor_impl.postprocess_item(&(*it)); + ++processed; + } + + m_last_post_proc_time = now; + + return processed; + } + + private: + LatencyBufferType& m_latency_buffer_impl; + RawDataProcessorType& m_raw_processor_impl; + const uint64_t m_processing_delay_ticks; // NOLINT(build/unsigned) + const uint64_t m_post_processing_delay_min_wait; // NOLINT(build/unsigned) + const uint64_t m_post_processing_delay_max_wait; // NOLINT(build/unsigned) + bool m_first_cycle; + RDT m_processed_up_to; + int m_consecutive_timeouts; + const timestamp_t m_max_wait_in_ticks; + std::chrono::time_point m_last_post_proc_time; + }; // Perform processing operations on payload void process_item(RDT&& payload); @@ -163,6 +284,12 @@ class DataHandlingModel : public DataHandlingConcept return { reinterpret_cast(original) }; } + // Actions postprocess scheduler takes if no data arrives in a configured time + virtual void invoke_postprocess_schedule_timeout_policy() const + { + return; // No-op for this class + } + // Operational monitoring virtual void generate_opmon_data() override; @@ -177,9 +304,9 @@ class DataHandlingModel : public DataHandlingConcept int m_current_fake_trigger_id; daqdataformats::SourceID m_sourceid; daqdataformats::run_number_t m_run_number; - uint64_t m_processing_delay_ticks; - uint64_t m_post_processing_delay_min_wait; - uint64_t m_post_processing_delay_max_wait; + uint64_t m_processing_delay_ticks; // NOLINT(build/unsigned) + uint64_t m_post_processing_delay_min_wait; // NOLINT(build/unsigned) + uint64_t m_post_processing_delay_max_wait; // NOLINT(build/unsigned) // STATS using metric_t = dunedaq::datahandlinglibs::opmon::DataHandlerInfo; @@ -228,7 +355,7 @@ class DataHandlingModel : public DataHandlingConcept // POSTPROCESS SCHEDULER utilities::ReusableThread m_postprocess_scheduler_thread; folly::coro::Baton m_baton; - std::unique_ptr m_timekeeper; + std::unique_ptr m_timekeeper; // LATENCY BUFFER std::shared_ptr m_latency_buffer_impl; diff --git a/include/datahandlinglibs/models/SkipListLatencyBufferModel.hpp b/include/datahandlinglibs/models/SkipListLatencyBufferModel.hpp index dd064be..b31cab6 100644 --- a/include/datahandlinglibs/models/SkipListLatencyBufferModel.hpp +++ b/include/datahandlinglibs/models/SkipListLatencyBufferModel.hpp @@ -35,6 +35,8 @@ class SkipListLatencyBufferModel : public LatencyBufferConcept using SkipListTAcc = typename folly::ConcurrentSkipList::Accessor; // SKL Accessor using SkipListTSkip = typename folly::ConcurrentSkipList::Skipper; // Skipper accessor + static constexpr bool supports_delayed_postprocessing = true; + // Constructor SkipListLatencyBufferModel() : m_skip_list(folly::ConcurrentSkipList::createInstance(unconfigured_head_height)) diff --git a/include/datahandlinglibs/models/TaskRawDataProcessorModel.hpp b/include/datahandlinglibs/models/TaskRawDataProcessorModel.hpp index 694d64a..c60616b 100644 --- a/include/datahandlinglibs/models/TaskRawDataProcessorModel.hpp +++ b/include/datahandlinglibs/models/TaskRawDataProcessorModel.hpp @@ -80,6 +80,9 @@ class TaskRawDataProcessorModel : public RawDataProcessorConcept // Registers ReadoutType item pointer to to the post-processing queue void postprocess_item(const ReadoutType* item) override; + // Handle a timeout event + void invoke_postprocess_schedule_timeout_policy(std::uint64_t accumilated_timeout_ticks) override {} + // Registers a pre-processing task to the pre-processor pipeline template void add_preprocess_task(Task&& task); diff --git a/include/datahandlinglibs/models/detail/DataHandlingModel.hxx b/include/datahandlinglibs/models/detail/DataHandlingModel.hxx index 3841506..4d102b3 100644 --- a/include/datahandlinglibs/models/detail/DataHandlingModel.hxx +++ b/include/datahandlinglibs/models/detail/DataHandlingModel.hxx @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include @@ -85,6 +88,13 @@ DataHandlingModel::init(const appmodel::DataHandlerModu m_post_processing_delay_min_wait = mcfg->get_module_configuration()->get_post_processing_delay_min_wait(); m_post_processing_delay_max_wait = mcfg->get_module_configuration()->get_post_processing_delay_max_wait(); + if (m_processing_delay_ticks) { + if constexpr (!SupportsDelayedPostprocessing) { + ers::error(ConfigurationError(ERS_HERE, m_sourceid, + "Delayed postprocessing (post_processing_delay_ticks > 0) requires a sorted buffer (SkipList). " + "Queue buffers (FixedRateQueue, BinarySearchQueue) expect in-order data and must use post_processing_delay_ticks = 0.")); + } + } // Configure implementations: m_raw_processor_impl->conf(mcfg); @@ -312,66 +322,52 @@ DataHandlingModel::run_consume() template folly::coro::Task -DataHandlingModel::postprocess_schedule() { +DataHandlingModel::postprocess_schedule() +{ TLOG_DEBUG(TLVL_WORK_STEPS) << "Postprocess schedule coroutine started..."; - timestamp_t newest_ts = 0; - timestamp_t end_win_ts = 0; - bool first_cycle = true; - auto last_post_proc_time = std::chrono::system_clock::now(); - auto now = last_post_proc_time; - std::chrono::milliseconds milliseconds; - RDT processed_element; - - // Deferral of the post processing, to allow elements being reordered in the LB - // Basically, find data older than a certain timestamp and process all data since the last post-processed element up to that value - while (m_run_marker.load()) { - try { - co_await folly::coro::timeout( - m_baton.operator co_await(), - std::chrono::milliseconds{m_post_processing_delay_max_wait}, - m_timekeeper.get()); - m_baton.reset(); - } catch (const folly::FutureTimeout&) { - ++m_num_post_processing_delay_max_waits; - } - - if (m_latency_buffer_impl->occupancy() == 0) { - continue; - } - - now = std::chrono::system_clock::now(); - milliseconds = std::chrono::duration_cast(now - last_post_proc_time); + TLOG() << "***** Starting post-process coroutine with timout " << m_post_processing_delay_max_wait << " *****"; + + + PostprocessScheduleAlgorithm sched_algo{ *m_latency_buffer_impl, + *m_raw_processor_impl, + m_processing_delay_ticks, + m_post_processing_delay_min_wait, + m_post_processing_delay_max_wait }; + + const auto wait_data = [this]() -> folly::coro::Task { + // folly::coro::timeout cancels the task on timeout. + // Baton is not cancellable, so we attach a callback to resume the coroutine. + auto token = co_await folly::coro::co_current_cancellation_token; + folly::CancellationCallback cb(token, [this] { m_baton.post(); }); + co_await m_baton; // Wait data + }; - if (milliseconds.count() <= m_post_processing_delay_min_wait) { - continue; + while (m_run_marker.load()) { + bool timeout = false; + + if ( m_post_processing_delay_max_wait > 0 ) { + try { + co_await folly::coro::timeout( + wait_data(), + std::chrono::milliseconds{ m_post_processing_delay_max_wait }, + m_timekeeper.get()); + + } catch (const folly::FutureTimeout&) { + timeout = true; + ++m_num_post_processing_delay_max_waits; + } + } else { + co_await m_baton; } - last_post_proc_time = now; - - // Get the LB boundaries - auto tail = m_latency_buffer_impl->back(); - newest_ts = tail->get_timestamp(); + m_baton.reset(); - if (first_cycle) { - auto head = m_latency_buffer_impl->front(); - processed_element.set_timestamp(head->get_timestamp()); - first_cycle = false; - TLOG() << "***** First pass post processing *****"; - } + if (auto processed = sched_algo.run(timeout); processed > 0) { + m_num_payloads += processed; + m_sum_payloads += processed; + m_stats_packet_count += processed; - if (newest_ts - processed_element.get_timestamp() > m_processing_delay_ticks) { - end_win_ts = newest_ts - m_processing_delay_ticks; - auto start_iter = m_latency_buffer_impl->lower_bound(processed_element, false); - processed_element.set_timestamp(end_win_ts); - auto end_iter = m_latency_buffer_impl->lower_bound(processed_element, false); - - for (auto it = start_iter; it != end_iter; ++it) { - m_raw_processor_impl->postprocess_item(&(*it)); - ++m_num_payloads; - ++m_sum_payloads; - ++m_stats_packet_count; - } } } } diff --git a/include/datahandlinglibs/models/detail/SkipListLatencyBufferModel.hxx b/include/datahandlinglibs/models/detail/SkipListLatencyBufferModel.hxx index efd5e67..bfe2a73 100644 --- a/include/datahandlinglibs/models/detail/SkipListLatencyBufferModel.hxx +++ b/include/datahandlinglibs/models/detail/SkipListLatencyBufferModel.hxx @@ -1,5 +1,7 @@ // Declarations for SkipListLatencyBufferModel +#include "datahandlinglibs/opmon/datahandling_info.pb.h" + namespace dunedaq { namespace datahandlinglibs { diff --git a/include/datahandlinglibs/testutils/UnitTestUtilities.hpp b/include/datahandlinglibs/testutils/UnitTestUtilities.hpp new file mode 100644 index 0000000..16673fd --- /dev/null +++ b/include/datahandlinglibs/testutils/UnitTestUtilities.hpp @@ -0,0 +1,61 @@ +/** + * @file UnitTestUtilities.hpp Unit test helper classes + * + * This is part of the DUNE DAQ Application Framework, copyright 2020. + * Licensing/copyright details are in the COPYING file that you should have + * received with this code. + */ + +#ifndef DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_UNITTESTUTILITIES_HPP +#define DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_UNITTESTUTILITIES_HPP + +#include "datahandlinglibs/models/DataHandlingModel.hpp" +#include "datahandlinglibs/models/DefaultRequestHandlerModel.hpp" +#include "datahandlinglibs/models/TaskRawDataProcessorModel.hpp" + +namespace dunedaq { +namespace datahandlinglibs { +namespace unittest { + +template +class MockDataHandlingModel + : public DataHandlingModel +{ +public: + using Base = + DataHandlingModel; + using Base::Base; + using Base::PostprocessScheduleAlgorithm; + using typename Base::num_post_processing_delay_max_waits_t; + + void test_run_postprocess_scheduler( + std::shared_ptr latency_buffer_impl, std::shared_ptr raw_processor_impl, + std::unique_ptr timekeeper, uint64_t post_processing_delay_max_wait) + { + this->m_latency_buffer_impl = latency_buffer_impl; + this->m_raw_processor_impl = raw_processor_impl; + this->m_timekeeper = std::move(timekeeper); + this->m_post_processing_delay_max_wait = post_processing_delay_max_wait; + this->run_postprocess_scheduler(); + } + + num_post_processing_delay_max_waits_t get_num_post_processing_delay_max_waits() + { + return this->m_num_post_processing_delay_max_waits.load(); + } + + void set_run_marker(bool run_marker) + { + return this->m_run_marker.store(run_marker); + } +}; + +} // namespace unittest +} // namespace datahandlinglibs +} // namespace dunedaq + +#endif // DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_UNITTESTUTILITIES_HPP diff --git a/test/apps/test_co_timeout_app.cxx b/test/apps/test_co_timeout_app.cxx new file mode 100644 index 0000000..6eb6633 --- /dev/null +++ b/test/apps/test_co_timeout_app.cxx @@ -0,0 +1,106 @@ +/** + * @file test_ratelimiter_app.cxx Test application for + * ratelimiter implementation + * + * This is part of the DUNE DAQ Application Framework, copyright 2020. + * Licensing/copyright details are in the COPYING file that you should have + * received with this code. + */ +# + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// using namespace dunedaq::datahandlinglibs; +using namespace std::chrono_literals; + +folly::coro::Baton baton{0}; +uint32_t max_wait = 500; + +folly::coro::Task +postprocess_schedule() { + + folly::ThreadWheelTimekeeper tk; + + const auto wait_data = [&baton]() -> folly::coro::Task { + // folly::coro::timeout cancels the task on timeout. + // Baton is not cancellable, so we attach a callback to resume the coroutine. + auto token = co_await folly::coro::co_current_cancellation_token; + folly::CancellationCallback cb(token, [&baton] { baton.post(); }); + co_await baton; // Wait data + }; + + + uint64_t n_timeouts = 0; + uint64_t n_process = 0; + + while(true) { + try { + co_await folly::coro::timeout( + wait_data(), + std::chrono::milliseconds{ max_wait }, + &tk); + ++n_process; + } catch (const folly::FutureTimeout&) { + // timeout = true; + std::cout << "Timeout " << ++n_timeouts << std::endl; + } + baton.reset(); + } + + + co_return; +} + +int +main(int /*argc*/, char** /*argv[]*/) +{ + std::atomic run_marker; + + + + + // A sleepy worker thread + std::jthread sleepy_worker( + [&baton](std::stop_token stoken) + { + + while(!stoken.stop_requested()) { + baton.post(); + } + // for (int i = 10; i; --i) + // { + // std::this_thread::sleep_for(300ms); + // if (stoken.stop_requested()) + // { + // print("Sleepy worker is requested to stop\n"); + // return; + // } + // print("Sleepy worker goes back to sleep\n"); + // } + }); + + // std::cout << "Sleeping for 3s" << std::endl; + + // std::this_thread::sleep_for(3s); + + std::cout << "Starting the coroutine" << std::endl; + folly::coro::blockingWait(postprocess_schedule()); + + std::cout << "Requesting stop" << std::endl; + // sleepy_worker.request_stop(); + std::cout << "Thread stopped" << std::endl; + + + return 0; +} // NOLINT(readability/fn_size) diff --git a/unittest/datahandlinglibs_DataHandlingModel_test.cxx b/unittest/datahandlinglibs_DataHandlingModel_test.cxx new file mode 100644 index 0000000..ebe8d48 --- /dev/null +++ b/unittest/datahandlinglibs_DataHandlingModel_test.cxx @@ -0,0 +1,131 @@ +/** + * @file datahandlinglibs_DataHandlingModel_test.cxx Unit Tests for DataHandlingModel + * + * This is part of the DUNE DAQ Application Framework, copyright 2020. + * Licensing/copyright details are in the COPYING file that you should have + * received with this code. + */ + +#define BOOST_TEST_MODULE datahandlinglibs_DataHandlingModel_test // NOLINT + +#include "boost/test/unit_test.hpp" + +#include "datahandlinglibs/ReadoutTypes.hpp" +#include "datahandlinglibs/models/SkipListLatencyBufferModel.hpp" +#include "datahandlinglibs/testutils/UnitTestUtilities.hpp" + +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE(datahandlinglibs_DataHandlingModel_test) + +using namespace dunedaq::datahandlinglibs; + +using ReadoutType = types::DUMMY_FRAME_STRUCT; + +BOOST_AUTO_TEST_CASE(datahandlinglibs_DataHandlingModel_run_postprocess_scheduler_timeout) +{ + std::atomic run_marker = true; + + auto model = + unittest::MockDataHandlingModel>, + SkipListLatencyBufferModel, + TaskRawDataProcessorModel>(run_marker); + + auto buffer = std::make_shared>(); // Empty buffer + + constexpr bool post_processing_enabled = true; + auto error_registry = std::make_unique(); + + auto raw_processor = + std::make_shared>(error_registry, post_processing_enabled); + + auto timekeeper = std::make_unique(); + auto* timekeeper_ptr = timekeeper.get(); + + constexpr uint64_t delay_max_wait = 2; // NOLINT(build/unsigned) + + std::thread coro_thread([&]() { + model.test_run_postprocess_scheduler(buffer, raw_processor, std::move(timekeeper), delay_max_wait); + }); + + // Wait for coroutine to start then timeout to get registered + while (timekeeper_ptr->numScheduled() == 0) { + std::this_thread::sleep_for(1ms); + } + // Safe-guard for the delay between timeout registration and coroutine suspension + // If the test is failing, consider a longer sleep or a better way to synchronize + std::this_thread::sleep_for(1ms); + timekeeper_ptr->advance(std::chrono::milliseconds{ delay_max_wait }); // Trigger a timeout + + model.set_run_marker(false); // Let coroutine end + coro_thread.join(); // The test will stuck here if timeout is not triggered (because of folly::coro::blockingWait) + + BOOST_REQUIRE_EQUAL(model.get_num_post_processing_delay_max_waits(), 1); +} + +BOOST_AUTO_TEST_CASE(datahandlinglibs_DataHandlingModel_PostprocessScheduleAlgorithm_timeout) +{ + std::atomic run_marker = true; + + auto model = + unittest::MockDataHandlingModel>, + SkipListLatencyBufferModel, + TaskRawDataProcessorModel>(run_marker); + + auto buffer = std::make_shared>(); + + for (int i = 1; i < 6; i++) { + ReadoutType frame{}; + frame.timestamp = i * 62500; + buffer->write(std::move(frame)); + } + + constexpr bool post_processing_enabled = true; + auto error_registry = std::make_unique(); + + auto raw_processor = + std::make_shared>(error_registry, post_processing_enabled); + + constexpr uint64_t delay_ticks = 4 * 62500; // NOLINT(build/unsigned) + constexpr uint64_t delay_min_wait = 1; // NOLINT(build/unsigned) + constexpr uint64_t delay_max_wait = 2; // NOLINT(build/unsigned) + + typename decltype(model)::PostprocessScheduleAlgorithm sched_algo{ + *buffer, *raw_processor, delay_ticks, delay_min_wait, delay_max_wait + }; + + // First pass + bool timeout = false; + int processed_count = sched_algo.run(timeout); + // Buffer = {1, 2, 3, 4, 5} delay_ticks = 4 + // 5 - 1 > 4 is false => no postprocessing + BOOST_REQUIRE_EQUAL(processed_count, 0); + + timeout = true; + // 1st timeout => timeout_accumulated = 1 * 2 (delay_max_wait = 2) + // end_win_ts = 5 - 4 + 2 => postprocess until 3 {1, 2} + processed_count += sched_algo.run(timeout); + BOOST_REQUIRE_EQUAL(processed_count, 2); + + // 2nd timeout => timeout_accumulated = 2 * 2 + // end_win_ts = 5 - 4 + 4 => postprocess until 5 {3, 4} + processed_count += sched_algo.run(timeout); + BOOST_REQUIRE_EQUAL(processed_count, 4); + + // 3rd timeout => timeout_accumulated = 3 * 2 + // end_win_ts = 5 - 4 + 6 => postprocess until 6 (capped to newest_ts + 1) {5} + processed_count += sched_algo.run(timeout); + BOOST_REQUIRE_EQUAL(processed_count, 5); + + // 4th timeout + // m_processed_up_to.timestamp = newest_ts + 1 => nothing to postprocess (at cap) + processed_count += sched_algo.run(timeout); + BOOST_REQUIRE_EQUAL(processed_count, 5); +} + +BOOST_AUTO_TEST_SUITE_END()