From b6acff5184eb163959ace321664a68716902cddb Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:19:22 +0000 Subject: [PATCH] FD guard into a separate file --- include/xrpl/server/FDGuard.h | 62 +++++++++++++++++++++++++ include/xrpl/server/detail/Door.h | 77 +------------------------------ src/libxrpl/server/FDGuard.cpp | 56 ++++++++++++++++++++++ 3 files changed, 120 insertions(+), 75 deletions(-) create mode 100644 include/xrpl/server/FDGuard.h create mode 100644 src/libxrpl/server/FDGuard.cpp diff --git a/include/xrpl/server/FDGuard.h b/include/xrpl/server/FDGuard.h new file mode 100644 index 0000000000..522a531414 --- /dev/null +++ b/include/xrpl/server/FDGuard.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#if !BOOST_OS_WINDOWS +#include + +#include +#include +#endif + +#include +#include + +namespace xrpl { + +/** + * FDGuard: File Descriptor monitoring and throttling helper + * + * Monitors system file descriptor usage and provides throttling + * decisions based on configurable thresholds. + * + * Thread-safe: All methods are const and stateless. + */ +class FDGuard +{ +public: + struct FDStats + { + std::uint64_t used{0}; // Currently open file descriptors + std::uint64_t limit{0}; // System limit (from getrlimit) + }; + + /** + * Query current file descriptor usage statistics. + * + * @return FDStats if available, std::nullopt on Windows or if query fails + * + * Implementation: + * - POSIX: Uses getrlimit(RLIMIT_NOFILE) for limit, + * counts entries in /proc/self/fd (Linux) or /dev/fd (BSD/macOS) + * - Windows: Always returns std::nullopt + */ + static std::optional + query_fd_stats(); + + /** + * Determine if system should throttle based on FD availability. + * + * @param free_threshold Minimum ratio of free FDs required (0.0 to 1.0) + * Default: 0.70 (require at least 70% free) + * @return true if free FDs below threshold (throttle recommended), + * false otherwise or if stats unavailable + * + * Example: threshold=0.70, limit=1000, used=800 + * free=200, ratio=0.20 < 0.70 → returns true (throttle) + */ + static bool + should_throttle(double free_threshold = 0.70); +}; + +} // namespace xrpl diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index e41049f203..7b063dce5f 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -17,14 +18,6 @@ #include #include #include -#include - -#if !BOOST_OS_WINDOWS -#include - -#include -#include -#endif #include #include @@ -94,23 +87,10 @@ private: static constexpr std::chrono::milliseconds MAX_ACCEPT_DELAY{2000}; std::chrono::milliseconds accept_delay_{INITIAL_ACCEPT_DELAY}; boost::asio::steady_timer backoff_timer_; - static constexpr double FREE_FD_THRESHOLD = 0.70; - - struct FDStats - { - std::uint64_t used{0}; - std::uint64_t limit{0}; - }; void reOpen(); - std::optional - query_fd_stats() const; - - bool - should_throttle_for_fds(); - public: Door(Handler& handler, boost::asio::io_context& io_context, Port const& port, beast::Journal j); @@ -317,7 +297,7 @@ Door::do_accept(boost::asio::yield_context do_yield) { while (acceptor_.is_open()) { - if (should_throttle_for_fds()) + if (FDGuard::should_throttle(0.70)) { backoff_timer_.expires_after(accept_delay_); boost::system::error_code tec; @@ -369,57 +349,4 @@ Door::do_accept(boost::asio::yield_context do_yield) } } -template -std::optional::FDStats> -Door::query_fd_stats() const -{ -#if BOOST_OS_WINDOWS - return std::nullopt; -#else - FDStats s; - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY) - return std::nullopt; - s.limit = static_cast(rl.rlim_cur); -#if BOOST_OS_LINUX - constexpr char const* kFdDir = "/proc/self/fd"; -#else - constexpr char const* kFdDir = "/dev/fd"; -#endif - if (DIR* d = ::opendir(kFdDir)) - { - std::uint64_t cnt = 0; - while (::readdir(d) != nullptr) - ++cnt; - ::closedir(d); - // readdir counts '.', '..', and the DIR* itself shows in the list - s.used = (cnt >= 3) ? (cnt - 3) : 0; - return s; - } - return std::nullopt; -#endif -} - -template -bool -Door::should_throttle_for_fds() -{ -#if BOOST_OS_WINDOWS - return false; -#else - auto const stats = query_fd_stats(); - if (!stats || stats->limit == 0) - return false; - - auto const& s = *stats; - auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull; - double const free_ratio = static_cast(free) / static_cast(s.limit); - if (free_ratio < FREE_FD_THRESHOLD) - { - return true; - } - return false; -#endif -} - } // namespace xrpl diff --git a/src/libxrpl/server/FDGuard.cpp b/src/libxrpl/server/FDGuard.cpp new file mode 100644 index 0000000000..c1da017daa --- /dev/null +++ b/src/libxrpl/server/FDGuard.cpp @@ -0,0 +1,56 @@ +#include + +namespace xrpl { + +std::optional +FDGuard::query_fd_stats() +{ +#if BOOST_OS_WINDOWS + return std::nullopt; +#else + FDStats s; + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) != 0 || rl.rlim_cur == RLIM_INFINITY) + return std::nullopt; + s.limit = static_cast(rl.rlim_cur); +#if BOOST_OS_LINUX + constexpr char const* kFdDir = "/proc/self/fd"; +#else + constexpr char const* kFdDir = "/dev/fd"; +#endif + if (DIR* d = ::opendir(kFdDir)) + { + std::uint64_t cnt = 0; + while (::readdir(d) != nullptr) + ++cnt; + ::closedir(d); + // readdir counts '.', '..', and the DIR* itself shows in the list + s.used = (cnt >= 3) ? (cnt - 3) : 0; + return s; + } + return std::nullopt; +#endif +} + +bool +FDGuard::should_throttle(double free_threshold) +{ +#if BOOST_OS_WINDOWS + return false; +#else + auto const stats = query_fd_stats(); + if (!stats || stats->limit == 0) + return false; + + auto const& s = *stats; + auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull; + double const free_ratio = static_cast(free) / static_cast(s.limit); + if (free_ratio < free_threshold) + { + return true; + } + return false; +#endif +} + +} // namespace xrpl