From f21964f729316ea19cf8594571303845eb5d43a3 Mon Sep 17 00:00:00 2001 From: Roman Stolyarchuk Date: Sat, 2 May 2026 21:12:33 +0300 Subject: [PATCH] refactor: change ifindex type from uint16_t to uint32_t in neighbor tasks - Updated function signatures in nl_control.hxx, nl_signal.hxx, and related task files to use uint32_t for ifindex. - Adjusted implementations in nl_control.cxx and task files to match the new signature. - Added validation function for received size in nl_request_task.hxx. - Introduced new test cases for neighbor tasks and request task validation. --- include/rtaco/core/nl_control.hxx | 18 ++-- include/rtaco/core/nl_signal.hxx | 7 +- .../rtaco/tasks/nl_neighbor_flush_task.hxx | 2 +- include/rtaco/tasks/nl_neighbor_get_task.hxx | 2 +- .../rtaco/tasks/nl_neighbor_probe_task.hxx | 2 +- include/rtaco/tasks/nl_request_task.hxx | 24 +++++- src/core/nl_control.cxx | 18 ++-- src/core/nl_listener.cxx | 23 ++--- src/tasks/nl_neighbor_dump_task.cxx | 3 +- src/tasks/nl_neighbor_flush_task.cxx | 5 +- src/tasks/nl_neighbor_get_task.cxx | 5 +- src/tasks/nl_neighbor_probe_task.cxx | 7 +- tests/CMakeLists.txt | 2 + tests/test_neighbor_tasks.cpp | 86 +++++++++++++++++++ tests/test_requesttask.cpp | 20 +++++ tests/test_signal.cpp | 23 +++++ 16 files changed, 199 insertions(+), 48 deletions(-) create mode 100644 tests/test_neighbor_tasks.cpp create mode 100644 tests/test_requesttask.cpp diff --git a/include/rtaco/core/nl_control.hxx b/include/rtaco/core/nl_control.hxx index 57a46b5..da6e99c 100644 --- a/include/rtaco/core/nl_control.hxx +++ b/include/rtaco/core/nl_control.hxx @@ -87,27 +87,27 @@ public: * @param address IPv6/IPv4 address bytes (16-byte span). * @return Expected void or error on failure. */ - auto probe_neighbor(uint16_t ifindex, std::span address) + auto probe_neighbor(uint32_t ifindex, std::span address) -> void_result_t; /** @brief Flush a neighbour entry (synchronous). */ - auto flush_neighbor(uint16_t ifindex, std::span address) + auto flush_neighbor(uint32_t ifindex, std::span address) -> void_result_t; /** @brief Get a neighbor entry synchronously. */ - auto get_neighbor(uint16_t ifindex, std::span address) + auto get_neighbor(uint32_t ifindex, std::span address) -> neighbor_result_t; /** @brief Asynchronously probe a neighbor. */ - auto async_probe_neighbor(uint16_t ifindex, std::span address) + auto async_probe_neighbor(uint32_t ifindex, std::span address) -> boost::asio::awaitable; /** @brief Asynchronously flush a neighbor. */ - auto async_flush_neighbor(uint16_t ifindex, std::span address) + auto async_flush_neighbor(uint32_t ifindex, std::span address) -> boost::asio::awaitable; /** @brief Asynchronously get a neighbor. */ - auto async_get_neighbor(uint16_t ifindex, std::span address) + auto async_get_neighbor(uint32_t ifindex, std::span address) -> boost::asio::awaitable; /** @brief Stop ongoing operations and release control resources. */ @@ -121,13 +121,13 @@ private: auto async_dump_links_impl() -> boost::asio::awaitable; auto async_dump_neighbors_impl() -> boost::asio::awaitable; - auto async_probe_neighbor_impl(uint16_t ifindex, std::span address) + auto async_probe_neighbor_impl(uint32_t ifindex, std::span address) -> boost::asio::awaitable; - auto async_flush_neighbor_impl(uint16_t ifindex, std::span address) + auto async_flush_neighbor_impl(uint32_t ifindex, std::span address) -> boost::asio::awaitable; - auto async_get_neighbor_impl(uint16_t ifindex, std::span address) + auto async_get_neighbor_impl(uint32_t ifindex, std::span address) -> boost::asio::awaitable; boost::asio::io_context& io_; diff --git a/include/rtaco/core/nl_signal.hxx b/include/rtaco/core/nl_signal.hxx index 514bf54..9cbce47 100644 --- a/include/rtaco/core/nl_signal.hxx +++ b/include/rtaco/core/nl_signal.hxx @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -139,9 +138,9 @@ public: std::apply(slot_fn, *args_pack); }; - boost::asio::co_spawn(executor, std::move(coroutine), - boost::asio::detached); - return detail::make_ready_shared_future(); + auto future = boost::asio::co_spawn(executor, std::move(coroutine), + boost::asio::use_future); + return future.share(); } auto coroutine = [slot_fn, args_pack, diff --git a/include/rtaco/tasks/nl_neighbor_flush_task.hxx b/include/rtaco/tasks/nl_neighbor_flush_task.hxx index bc15fe5..99ab667 100644 --- a/include/rtaco/tasks/nl_neighbor_flush_task.hxx +++ b/include/rtaco/tasks/nl_neighbor_flush_task.hxx @@ -32,7 +32,7 @@ public: * @param sequence Netlink message sequence number. * @param address Address bytes to identify the neighbor. */ - NeighborFlushTask(SocketGuard& socket_guard, uint16_t ifindex, uint32_t sequence, + NeighborFlushTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address); /** @brief Prepare the netlink request to flush the neighbor entry. */ diff --git a/include/rtaco/tasks/nl_neighbor_get_task.hxx b/include/rtaco/tasks/nl_neighbor_get_task.hxx index 68e71ee..1e3cfc0 100644 --- a/include/rtaco/tasks/nl_neighbor_get_task.hxx +++ b/include/rtaco/tasks/nl_neighbor_get_task.hxx @@ -33,7 +33,7 @@ public: * @param sequence Netlink message sequence number. * @param address Address bytes identifying the neighbor. */ - NeighborGetTask(SocketGuard& socket_guard, uint16_t uint16_t, uint32_t sequence, + NeighborGetTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address); /** @brief Prepare the netlink request to get the neighbor. */ diff --git a/include/rtaco/tasks/nl_neighbor_probe_task.hxx b/include/rtaco/tasks/nl_neighbor_probe_task.hxx index 4d32b11..fea6957 100644 --- a/include/rtaco/tasks/nl_neighbor_probe_task.hxx +++ b/include/rtaco/tasks/nl_neighbor_probe_task.hxx @@ -32,7 +32,7 @@ public: * @param sequence Netlink message sequence number. * @param address Address bytes to probe. */ - NeighborProbeTask(SocketGuard& socket_guard, uint16_t uint16_t, uint32_t sequence, + NeighborProbeTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address); /** @brief Prepare the netlink request to probe the neighbor. */ diff --git a/include/rtaco/tasks/nl_request_task.hxx b/include/rtaco/tasks/nl_request_task.hxx index aab4517..6ef370a 100644 --- a/include/rtaco/tasks/nl_request_task.hxx +++ b/include/rtaco/tasks/nl_request_task.hxx @@ -23,6 +23,19 @@ namespace llmx { namespace rtaco { +namespace detail { + +inline auto validate_received_size(size_t bytes, size_t capacity) + -> std::expected { + if (bytes >= capacity) { + return std::unexpected(std::make_error_code(std::errc::message_size)); + } + + return {}; +} + +} // namespace detail + template concept request_behavior = requires(Derived& derived, const Derived& const_derived, const nlmsghdr& header) { @@ -48,7 +61,7 @@ class RequestTask { std::array receive_buffer_; SocketGuard& socket_guard_; - uint16_t ifindex_; + uint32_t ifindex_; uint32_t sequence_; public: @@ -58,7 +71,7 @@ public: * @param ifindex Interface index associated with the request. * @param sequence Netlink sequence number for messages. */ - RequestTask(SocketGuard& socket_guard, uint16_t ifindex, uint32_t sequence) noexcept + RequestTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence) noexcept : socket_guard_{socket_guard} , ifindex_{ifindex} , sequence_{sequence} {} @@ -90,7 +103,7 @@ protected: return sequence_; } - auto ifindex() const noexcept -> uint16_t { + auto ifindex() const noexcept -> uint32_t { return ifindex_; } @@ -136,7 +149,10 @@ private: std::error_code{ec.value(), std::generic_category()}); } - if (bytes >= receive_buffer_.size()) { + if (auto size_ok = detail::validate_received_size( + bytes, receive_buffer_.size()); + !size_ok) { + co_return std::unexpected(size_ok.error()); } auto remaining = static_cast(bytes); diff --git a/src/core/nl_control.cxx b/src/core/nl_control.cxx index a5e0d25..83568ff 100644 --- a/src/core/nl_control.cxx +++ b/src/core/nl_control.cxx @@ -95,7 +95,7 @@ auto Control::async_dump_neighbors() asio::use_awaitable); } -auto Control::flush_neighbor(uint16_t ifindex, std::span address) +auto Control::flush_neighbor(uint32_t ifindex, std::span address) -> std::expected { auto future = asio::co_spawn(strand_, async_flush_neighbor_impl(ifindex, address), asio::use_future); @@ -103,13 +103,13 @@ auto Control::flush_neighbor(uint16_t ifindex, std::span address) return future.get(); } -auto Control::async_flush_neighbor(uint16_t ifindex, std::span address) +auto Control::async_flush_neighbor(uint32_t ifindex, std::span address) -> asio::awaitable> { co_return co_await asio::co_spawn(strand_, async_flush_neighbor_impl(ifindex, address), asio::use_awaitable); } -auto Control::probe_neighbor(uint16_t ifindex, std::span address) +auto Control::probe_neighbor(uint32_t ifindex, std::span address) -> std::expected { auto future = asio::co_spawn(strand_, async_probe_neighbor_impl(ifindex, address), asio::use_future); @@ -117,13 +117,13 @@ auto Control::probe_neighbor(uint16_t ifindex, std::span address) return future.get(); } -auto Control::async_probe_neighbor(uint16_t ifindex, std::span address) +auto Control::async_probe_neighbor(uint32_t ifindex, std::span address) -> asio::awaitable> { co_return co_await asio::co_spawn(strand_, async_probe_neighbor_impl(ifindex, address), asio::use_awaitable); } -auto Control::get_neighbor(uint16_t ifindex, std::span address) +auto Control::get_neighbor(uint32_t ifindex, std::span address) -> std::expected { auto future = asio::co_spawn(strand_, async_get_neighbor_impl(ifindex, address), asio::use_future); @@ -131,7 +131,7 @@ auto Control::get_neighbor(uint16_t ifindex, std::span address) return future.get(); } -auto Control::async_get_neighbor(uint16_t ifindex, std::span address) +auto Control::async_get_neighbor(uint32_t ifindex, std::span address) -> asio::awaitable> { co_return co_await asio::co_spawn(strand_, async_get_neighbor_impl(ifindex, address), asio::use_awaitable); @@ -209,7 +209,7 @@ auto Control::async_dump_links_impl() -> asio::awaitable { co_return co_await task.async_run(); } -auto Control::async_probe_neighbor_impl(uint16_t ifindex, std::span address) +auto Control::async_probe_neighbor_impl(uint32_t ifindex, std::span address) -> asio::awaitable { if (auto gate_error = co_await acquire_socket_token(); gate_error) { co_return std::unexpected{gate_error}; @@ -225,7 +225,7 @@ auto Control::async_probe_neighbor_impl(uint16_t ifindex, std::span co_return co_await task.async_run(); } -auto Control::async_flush_neighbor_impl(uint16_t ifindex, std::span address) +auto Control::async_flush_neighbor_impl(uint32_t ifindex, std::span address) -> asio::awaitable { if (auto gate_error = co_await acquire_socket_token(); gate_error) { co_return std::unexpected{gate_error}; @@ -241,7 +241,7 @@ auto Control::async_flush_neighbor_impl(uint16_t ifindex, std::span co_return co_await task.async_run(); } -auto Control::async_get_neighbor_impl(uint16_t ifindex, std::span address) +auto Control::async_get_neighbor_impl(uint32_t ifindex, std::span address) -> asio::awaitable { if (auto gate_error = co_await acquire_socket_token(); gate_error) { co_return std::unexpected{gate_error}; diff --git a/src/core/nl_listener.cxx b/src/core/nl_listener.cxx index 0e94bfa..3deb1e4 100644 --- a/src/core/nl_listener.cxx +++ b/src/core/nl_listener.cxx @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -136,17 +137,17 @@ void Listener::process_messages(std::span data) { void Listener::handle_message(const nlmsghdr& header) { using HandlerEntry12 = std::pair; - static std::unordered_map handlers; - - handlers[RTM_NEWLINK] = {sizeof(ifinfomsg), &Listener::handle_link_message}; - handlers[RTM_DELLINK] = {sizeof(ifinfomsg), &Listener::handle_link_message}; - handlers[RTM_NEWADDR] = {sizeof(ifaddrmsg), &Listener::handle_address_message}; - handlers[RTM_DELADDR] = {sizeof(ifaddrmsg), &Listener::handle_address_message}; - handlers[RTM_NEWROUTE] = {sizeof(rtmsg), &Listener::handle_route_message}; - handlers[RTM_DELROUTE] = {sizeof(rtmsg), &Listener::handle_route_message}; - handlers[RTM_NEWNEIGH] = {sizeof(ndmsg), &Listener::handle_neighbor_message}; - handlers[RTM_DELNEIGH] = {sizeof(ndmsg), &Listener::handle_neighbor_message}; - handlers[NLMSG_ERROR] = {sizeof(nlmsgerr), &Listener::handle_error_message}; + static const std::unordered_map handlers = { + {RTM_NEWLINK, {sizeof(ifinfomsg), &Listener::handle_link_message}}, + {RTM_DELLINK, {sizeof(ifinfomsg), &Listener::handle_link_message}}, + {RTM_NEWADDR, {sizeof(ifaddrmsg), &Listener::handle_address_message}}, + {RTM_DELADDR, {sizeof(ifaddrmsg), &Listener::handle_address_message}}, + {RTM_NEWROUTE, {sizeof(rtmsg), &Listener::handle_route_message}}, + {RTM_DELROUTE, {sizeof(rtmsg), &Listener::handle_route_message}}, + {RTM_NEWNEIGH, {sizeof(ndmsg), &Listener::handle_neighbor_message}}, + {RTM_DELNEIGH, {sizeof(ndmsg), &Listener::handle_neighbor_message}}, + {NLMSG_ERROR, {sizeof(nlmsgerr), &Listener::handle_error_message}}, + }; if (!handlers.contains(header.nlmsg_type)) { return; diff --git a/src/tasks/nl_neighbor_dump_task.cxx b/src/tasks/nl_neighbor_dump_task.cxx index 5806087..ca61296 100644 --- a/src/tasks/nl_neighbor_dump_task.cxx +++ b/src/tasks/nl_neighbor_dump_task.cxx @@ -15,6 +15,7 @@ #include #include "rtaco/events/nl_neighbor_event.hxx" +#include "rtaco/core/nl_common.hxx" #include "rtaco/tasks/nl_neighbor_task.hxx" namespace llmx { @@ -53,7 +54,7 @@ auto NeighborDumpTask::handle_done() auto NeighborDumpTask::handle_error(const nlmsghdr& header) -> std::expected { - const auto* err = reinterpret_cast(NLMSG_DATA(&header)); + const auto* err = checked_nlmsgerr(header); const auto code = err != nullptr ? -err->error : EPROTO; const auto error_code = std::make_error_code(static_cast(code)); diff --git a/src/tasks/nl_neighbor_flush_task.cxx b/src/tasks/nl_neighbor_flush_task.cxx index b3ad907..c6b31c9 100644 --- a/src/tasks/nl_neighbor_flush_task.cxx +++ b/src/tasks/nl_neighbor_flush_task.cxx @@ -11,11 +11,12 @@ #include #include "rtaco/tasks/nl_neighbor_task.hxx" +#include "rtaco/core/nl_common.hxx" namespace llmx { namespace rtaco { -NeighborFlushTask::NeighborFlushTask(SocketGuard& socket_guard, uint16_t ifindex, +NeighborFlushTask::NeighborFlushTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address) : NeighborTask{socket_guard, ifindex, sequence} { for (std::size_t i = 0; i < 16; ++i) { @@ -42,7 +43,7 @@ auto NeighborFlushTask::process_message(const nlmsghdr& header) auto NeighborFlushTask::handle_error(const nlmsghdr& header) -> std::expected { - const auto* err = reinterpret_cast(NLMSG_DATA(&header)); + const auto* err = checked_nlmsgerr(header); const auto code = err != nullptr ? -err->error : EPROTO; const auto error_code = std::make_error_code(static_cast(code)); diff --git a/src/tasks/nl_neighbor_get_task.cxx b/src/tasks/nl_neighbor_get_task.cxx index 0ba90d4..f56eb06 100644 --- a/src/tasks/nl_neighbor_get_task.cxx +++ b/src/tasks/nl_neighbor_get_task.cxx @@ -13,12 +13,13 @@ #include #include "rtaco/events/nl_neighbor_event.hxx" +#include "rtaco/core/nl_common.hxx" #include "rtaco/tasks/nl_neighbor_task.hxx" namespace llmx { namespace rtaco { -NeighborGetTask::NeighborGetTask(SocketGuard& socket_guard, uint16_t ifindex, +NeighborGetTask::NeighborGetTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address) : NeighborTask{socket_guard, ifindex, sequence} { for (std::size_t i = 0; i < 16; ++i) { @@ -50,7 +51,7 @@ auto NeighborGetTask::handle_done() -> std::expected std::expected { - const auto* err = reinterpret_cast(NLMSG_DATA(&header)); + const auto* err = checked_nlmsgerr(header); const auto code = err != nullptr ? -err->error : EPROTO; const auto error_code = std::make_error_code(static_cast(code)); diff --git a/src/tasks/nl_neighbor_probe_task.cxx b/src/tasks/nl_neighbor_probe_task.cxx index dbfdb50..0f5b6f3 100644 --- a/src/tasks/nl_neighbor_probe_task.cxx +++ b/src/tasks/nl_neighbor_probe_task.cxx @@ -12,13 +12,14 @@ #include #include "rtaco/tasks/nl_neighbor_task.hxx" +#include "rtaco/core/nl_common.hxx" namespace llmx { namespace rtaco { -NeighborProbeTask::NeighborProbeTask(SocketGuard& socket_guard, uint16_t uint16_t, +NeighborProbeTask::NeighborProbeTask(SocketGuard& socket_guard, uint32_t ifindex, uint32_t sequence, std::span address) - : NeighborTask{socket_guard, uint16_t, sequence} { + : NeighborTask{socket_guard, ifindex, sequence} { for (std::size_t i = 0; i < 16; ++i) { address_[i] = address[i]; } @@ -44,7 +45,7 @@ auto NeighborProbeTask::process_message(const nlmsghdr& header) auto NeighborProbeTask::handle_error(const nlmsghdr& header) -> std::expected { - const auto* err = reinterpret_cast(NLMSG_DATA(&header)); + const auto* err = checked_nlmsgerr(header); const auto code = err != nullptr ? -err->error : EPROTO; const auto error_code = std::make_error_code(static_cast(code)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7e3d761..c05e5e7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,9 +41,11 @@ FetchContent_MakeAvailable(googletest) add_executable(test_rtaco test_signal.cpp test_requesttask_compile.cpp + test_requesttask.cpp test_socket.cpp test_control.cpp test_nl_common.cpp + test_neighbor_tasks.cpp ) set(rtaco_test_sources) diff --git a/tests/test_neighbor_tasks.cpp b/tests/test_neighbor_tasks.cpp new file mode 100644 index 0000000..80b787b --- /dev/null +++ b/tests/test_neighbor_tasks.cpp @@ -0,0 +1,86 @@ +#include + +#include +#include + +#include + +#include "rtaco/socket/nl_socket_guard.hxx" +#include "rtaco/tasks/nl_neighbor_dump_task.hxx" +#include "rtaco/tasks/nl_neighbor_flush_task.hxx" +#include "rtaco/tasks/nl_neighbor_get_task.hxx" +#include "rtaco/tasks/nl_neighbor_probe_task.hxx" + +using namespace llmx::rtaco; + +namespace { + +auto make_malformed_error(uint32_t seq) -> std::vector { + std::vector buf(NLMSG_SPACE(sizeof(nlmsgerr)) - 1, 0); + auto* header = reinterpret_cast(buf.data()); + header->nlmsg_len = NLMSG_LENGTH(sizeof(nlmsgerr)) - 1; + header->nlmsg_type = NLMSG_ERROR; + header->nlmsg_seq = seq; + return buf; +} + +} // namespace + +TEST(NeighborTaskTest, GetTaskMalformedErrorReturnsEproto) { + boost::asio::io_context io; + SocketGuard guard{io, "test-neigh-get"}; + std::array addr{}; + NeighborGetTask task{guard, 1, 77, std::span{addr}}; + + auto message = make_malformed_error(77); + const auto* header = reinterpret_cast(message.data()); + + auto result = task.process_message(*header); + ASSERT_TRUE(result.has_value()); + ASSERT_FALSE(result->has_value()); + EXPECT_EQ(result->error(), std::make_error_code(std::errc::protocol_error)); +} + +TEST(NeighborTaskTest, DumpTaskMalformedErrorReturnsEproto) { + boost::asio::io_context io; + SocketGuard guard{io, "test-neigh-dump"}; + NeighborDumpTask task{guard, std::pmr::get_default_resource(), 1, 78}; + + auto message = make_malformed_error(78); + const auto* header = reinterpret_cast(message.data()); + + auto result = task.process_message(*header); + ASSERT_TRUE(result.has_value()); + ASSERT_FALSE(result->has_value()); + EXPECT_EQ(result->error(), std::make_error_code(std::errc::protocol_error)); +} + +TEST(NeighborTaskTest, FlushTaskMalformedErrorReturnsEproto) { + boost::asio::io_context io; + SocketGuard guard{io, "test-neigh-flush"}; + std::array addr{}; + NeighborFlushTask task{guard, 1, 79, std::span{addr}}; + + auto message = make_malformed_error(79); + const auto* header = reinterpret_cast(message.data()); + + auto result = task.process_message(*header); + ASSERT_TRUE(result.has_value()); + ASSERT_FALSE(result->has_value()); + EXPECT_EQ(result->error(), std::make_error_code(std::errc::protocol_error)); +} + +TEST(NeighborTaskTest, ProbeTaskMalformedErrorReturnsEproto) { + boost::asio::io_context io; + SocketGuard guard{io, "test-neigh-probe"}; + std::array addr{}; + NeighborProbeTask task{guard, 1, 80, std::span{addr}}; + + auto message = make_malformed_error(80); + const auto* header = reinterpret_cast(message.data()); + + auto result = task.process_message(*header); + ASSERT_TRUE(result.has_value()); + ASSERT_FALSE(result->has_value()); + EXPECT_EQ(result->error(), std::make_error_code(std::errc::protocol_error)); +} diff --git a/tests/test_requesttask.cpp b/tests/test_requesttask.cpp new file mode 100644 index 0000000..0117bed --- /dev/null +++ b/tests/test_requesttask.cpp @@ -0,0 +1,20 @@ +#include + +#include + +#include "rtaco/tasks/nl_request_task.hxx" + +using namespace llmx::rtaco; + +TEST(RequestTaskTest, ValidateReceivedSizeRejectsFullBuffer) { + constexpr size_t kCapacity = 4096; + auto result = detail::validate_received_size(kCapacity, kCapacity); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), std::make_error_code(std::errc::message_size)); +} + +TEST(RequestTaskTest, ValidateReceivedSizeAcceptsPartialBuffer) { + constexpr size_t kCapacity = 4096; + auto result = detail::validate_received_size(kCapacity - 1, kCapacity); + EXPECT_TRUE(result.has_value()); +} diff --git a/tests/test_signal.cpp b/tests/test_signal.cpp index 3448ded..4a8f15f 100644 --- a/tests/test_signal.cpp +++ b/tests/test_signal.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "rtaco/core/nl_signal.hxx" @@ -31,3 +32,25 @@ TEST(SignalTest, SyncAndAsyncSlots) { io.stop(); runner.join(); } + +TEST(SignalTest, VoidAsyncSlotCompletesBeforeEmitReturns) { + boost::asio::io_context io; + Signal sig(io.get_executor()); + + std::atomic_bool ran{false}; + auto conn = sig.connect([&ran](int value) { + if (value == 7) { + ran.store(true, std::memory_order_release); + } + }, ExecPolicy::Async); + + auto work = boost::asio::make_work_guard(io); + std::thread runner([&io] { io.run(); }); + + sig.emit(7); + EXPECT_TRUE(ran.load(std::memory_order_acquire)); + + work.reset(); + io.stop(); + runner.join(); +}