Compare commits

..

2 Commits

Author SHA1 Message Date
Valentin Balaschenko
4f96a5fa48 Merge branch 'develop' into vlntb/grpc-fd-guard 2026-02-18 15:33:07 +00:00
Valentin Balaschenko
b6acff5184 FD guard into a separate file 2026-02-18 11:19:22 +00:00
3 changed files with 120 additions and 75 deletions

View File

@@ -0,0 +1,62 @@
#pragma once
#include <boost/predef.h>
#if !BOOST_OS_WINDOWS
#include <sys/resource.h>
#include <dirent.h>
#include <unistd.h>
#endif
#include <cstdint>
#include <optional>
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<FDStats>
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

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/server/FDGuard.h>
#include <xrpl/server/detail/PlainHTTPPeer.h>
#include <xrpl/server/detail/SSLHTTPPeer.h>
#include <xrpl/server/detail/io_list.h>
@@ -17,14 +18,6 @@
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/predef.h>
#if !BOOST_OS_WINDOWS
#include <sys/resource.h>
#include <dirent.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <chrono>
@@ -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<FDStats>
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<Handler>::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<Handler>::do_accept(boost::asio::yield_context do_yield)
}
}
template <class Handler>
std::optional<typename Door<Handler>::FDStats>
Door<Handler>::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<std::uint64_t>(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 <class Handler>
bool
Door<Handler>::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<double>(free) / static_cast<double>(s.limit);
if (free_ratio < FREE_FD_THRESHOLD)
{
return true;
}
return false;
#endif
}
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#include <xrpl/server/FDGuard.h>
namespace xrpl {
std::optional<FDGuard::FDStats>
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<std::uint64_t>(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<double>(free) / static_cast<double>(s.limit);
if (free_ratio < free_threshold)
{
return true;
}
return false;
#endif
}
} // namespace xrpl