From 4167a127580cf359082d25dcc830919061d6f090 Mon Sep 17 00:00:00 2001 From: Xudong Han <42986190+han-xudong@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:28:13 +0800 Subject: [PATCH] Fix receive timeout across EINTR retries --- src/detail/posix_transport.cpp | 21 +++++++--- test/CMakeLists.txt | 15 +++++++ test/test_posix_transport.cpp | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 test/test_posix_transport.cpp diff --git a/src/detail/posix_transport.cpp b/src/detail/posix_transport.cpp index 26620f7..5943450 100644 --- a/src/detail/posix_transport.cpp +++ b/src/detail/posix_transport.cpp @@ -96,12 +96,23 @@ std::size_t PosixTransport::receive(std::uint8_t *data, const std::size_t capaci } pollfd descriptor{socket, POLLIN, 0}; + const auto deadline = std::chrono::steady_clock::now() + timeout; + auto remaining = timeout; int poll_result{}; - do { - poll_result = ::poll(&descriptor, 1, timeout_milliseconds(timeout)); - } while (poll_result < 0 && errno == EINTR); - if (poll_result < 0) { - throw socket_error("failed to wait for UDP record"); + while (true) { + poll_result = ::poll(&descriptor, 1, timeout_milliseconds(remaining)); + if (poll_result >= 0) { + break; + } + if (errno != EINTR) { + throw socket_error("failed to wait for UDP record"); + } + + const auto now = std::chrono::steady_clock::now(); + if (now >= deadline) { + return 0; + } + remaining = deadline - now; } if (poll_result == 0) { return 0; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f86dc55..a2fc7f7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,21 @@ target_link_libraries(netft_fault_latch PRIVATE netft::netft ${NETFT_GTEST_MAIN_ add_test(NAME netft_fault_latch COMMAND netft_fault_latch) +add_executable(netft_posix_transport + test_posix_transport.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/detail/posix_transport.cpp +) +target_compile_features(netft_posix_transport PRIVATE cxx_std_17) +target_include_directories(netft_posix_transport PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src +) +target_link_options(netft_posix_transport PRIVATE + "-Wl,--wrap=poll" +) +target_link_libraries(netft_posix_transport PRIVATE ${NETFT_GTEST_MAIN_TARGET}) + +add_test(NAME netft_posix_transport COMMAND netft_posix_transport) + add_executable(netft_discovery test_discovery.cpp support/fake_http_server.cpp diff --git a/test/test_posix_transport.cpp b/test/test_posix_transport.cpp new file mode 100644 index 0000000..8bea8a0 --- /dev/null +++ b/test/test_posix_transport.cpp @@ -0,0 +1,72 @@ +#include "detail/posix_transport.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace std::chrono_literals; + +enum class PollBehavior { RepeatedEintrThenTimeout, Error }; + +constexpr int kInterruptCount = 3; +std::vector poll_timeouts; +int poll_calls{}; +PollBehavior poll_behavior{PollBehavior::RepeatedEintrThenTimeout}; + +} // namespace + +extern "C" int __wrap_poll(pollfd *, nfds_t, const int timeout) { + poll_timeouts.push_back(timeout); + if (poll_behavior == PollBehavior::Error) { + errno = EBADF; + return -1; + } + if (poll_calls++ < kInterruptCount) { + std::this_thread::sleep_for(10ms); + errno = EINTR; + return -1; + } + + std::this_thread::sleep_for(std::chrono::milliseconds{timeout}); + return 0; +} + +TEST(PosixTransportTest, RepeatedEintrPreservesOriginalTimeout) { + poll_timeouts.clear(); + poll_calls = 0; + poll_behavior = PollBehavior::RepeatedEintrThenTimeout; + + netft::detail::PosixTransport transport; + transport.connect("127.0.0.1", 49152); + std::array buffer{}; + + EXPECT_EQ(transport.receive(buffer.data(), buffer.size(), 100ms), 0U); + + ASSERT_EQ(poll_timeouts.size(), static_cast(kInterruptCount + 1)); + for (std::size_t index = 1; index < poll_timeouts.size(); ++index) { + EXPECT_LT(poll_timeouts[index], poll_timeouts[index - 1]); + } +} + +TEST(PosixTransportTest, NonEintrPollErrorStillThrows) { + poll_timeouts.clear(); + poll_calls = 0; + poll_behavior = PollBehavior::Error; + + netft::detail::PosixTransport transport; + transport.connect("127.0.0.1", 49152); + std::array buffer{}; + + EXPECT_THROW(transport.receive(buffer.data(), buffer.size(), 100ms), std::runtime_error); + EXPECT_EQ(poll_timeouts.size(), 1U); +}