#include #include #include #include #include #include #include #include BOOST_AUTO_TEST_CASE(session_drain_handles_notifications_before_and_during_registration) { for (int iteration = 0; iteration < 64; ++iteration) { auto io = boost::asio::io_context{}; auto registry = ligo::detail::session_registry{}; auto session = std::make_shared(1); registry.add(session); auto result = boost::asio::co_spawn(io, registry.wait_empty(std::chrono::seconds{2}), boost::asio::use_future); auto work = boost::asio::make_work_guard(io); auto first = std::thread{[&] { io.run(); }}; auto second = std::thread{[&] { io.run(); }}; registry.remove(session); auto ready = result.wait_for(std::chrono::milliseconds{500}) == std::future_status::ready; work.reset(); io.stop(); first.join(); second.join(); BOOST_REQUIRE(ready); BOOST_CHECK(result.get()); } } BOOST_AUTO_TEST_CASE(session_drain_keeps_absolute_deadline_under_repeated_notifications) { auto io = boost::asio::io_context{}; auto registry = ligo::detail::session_registry{}; auto session = std::make_shared(1); registry.add(session); auto result = boost::asio::co_spawn(io, registry.wait_empty(std::chrono::milliseconds{20}), boost::asio::use_future); auto work = boost::asio::make_work_guard(io); auto first = std::thread{[&] { io.run(); }}; auto second = std::thread{[&] { io.run(); }}; for (int i = 0; i < 200 && result.wait_for(std::chrono::milliseconds{1}) != std::future_status::ready; ++i) { registry.notify_waiters(); } auto ready = result.wait_for(std::chrono::milliseconds{0}) == std::future_status::ready; work.reset(); io.stop(); first.join(); second.join(); BOOST_REQUIRE(ready); auto value = result.get(); BOOST_REQUIRE(!value); BOOST_CHECK(value.error().code() == ligo::detail::error_timeout); } BOOST_AUTO_TEST_CASE(session_drain_observes_expiry_and_removal_before_wait_is_armed) { auto io = boost::asio::io_context{}; auto registry = ligo::detail::session_registry{}; auto session = std::make_shared(1); registry.add(session); auto timer = std::make_shared(boost::asio::make_strand(io)); registry.waiters.push_back(timer); // A notification is queued before registration. It must neither extend an // elapsed deadline nor prevent observing a session that already finished. registry.notify_waiters(); auto expired = boost::asio::co_spawn(timer->get_executor(), registry.wait_registered(timer, std::chrono::steady_clock::now()), boost::asio::use_future); io.run(); auto result = expired.get(); BOOST_REQUIRE(!result); BOOST_CHECK(result.error().code() == ligo::detail::error_timeout); io.restart(); registry.remove(session); auto drained = boost::asio::co_spawn(timer->get_executor(), registry.wait_registered(timer, std::chrono::steady_clock::now() + std::chrono::hours{1}), boost::asio::use_future); io.run(); BOOST_CHECK(drained.get()); }