Skip to content

Commit 3477308

Browse files
authored
Merge branch 'develop' into tapanito/lending-fix-amendment
2 parents 3c3bd75 + 65e63eb commit 3477308

File tree

7 files changed

+446
-4
lines changed

7 files changed

+446
-4
lines changed

.github/workflows/reusable-build-test-config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
steps:
102102
- name: Cleanup workspace (macOS and Windows)
103103
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
104-
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
104+
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
105105

106106
- name: Checkout repository
107107
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

.github/workflows/upload-conan-deps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
steps:
6565
- name: Cleanup workspace (macOS and Windows)
6666
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
67-
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
67+
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
6868

6969
- name: Checkout repository
7070
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

include/xrpl/basics/MallocTrim.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#pragma once
2+
3+
#include <xrpl/beast/utility/Journal.h>
4+
5+
#include <chrono>
6+
#include <cstdint>
7+
#include <string_view>
8+
9+
namespace xrpl {
10+
11+
// cSpell:ignore ptmalloc
12+
13+
// -----------------------------------------------------------------------------
14+
// Allocator interaction note:
15+
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
16+
// ptmalloc return free heap pages to the OS.
17+
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
18+
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
19+
// on the *active* heap. The call is harmless but may not reclaim memory
20+
// because those allocators manage their own arenas.
21+
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
22+
// allocations are usually returned to the OS on free regardless of trimming.
23+
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
24+
// and consider rate limiting to avoid churn.
25+
// -----------------------------------------------------------------------------
26+
27+
struct MallocTrimReport
28+
{
29+
bool supported{false};
30+
int trimResult{-1};
31+
std::int64_t rssBeforeKB{-1};
32+
std::int64_t rssAfterKB{-1};
33+
std::chrono::microseconds durationUs{-1};
34+
std::int64_t minfltDelta{-1};
35+
std::int64_t majfltDelta{-1};
36+
37+
[[nodiscard]] std::int64_t
38+
deltaKB() const noexcept
39+
{
40+
if (rssBeforeKB < 0 || rssAfterKB < 0)
41+
return 0;
42+
return rssAfterKB - rssBeforeKB;
43+
}
44+
};
45+
46+
/**
47+
* @brief Attempt to return freed memory to the operating system.
48+
*
49+
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
50+
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
51+
* a different allocator is in use, this function is a no-op and the report will
52+
* indicate that trimming is unsupported or had no effect.
53+
*
54+
* @param tag Identifier for logging/debugging purposes.
55+
* @param journal Journal for diagnostic logging.
56+
* @return Report containing before/after metrics and the trim result.
57+
*
58+
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
59+
* calling glibc's malloc_trim may have no effect on the active heap. The
60+
* call is harmless but typically does not reclaim memory under those
61+
* allocators.
62+
*
63+
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
64+
* Large allocations satisfied via mmap are usually returned on free
65+
* independently of trimming.
66+
*
67+
* @note Intended for use after operations that free significant memory (e.g.,
68+
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
69+
*/
70+
MallocTrimReport
71+
mallocTrim(std::string_view tag, beast::Journal journal);
72+
73+
} // namespace xrpl

include/xrpl/protocol/detail/features.macro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
XRPL_FIX (LendingProtocolV1_1, Supported::yes, VoteBehavior::DefaultNo)
2020
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
2121
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
22-
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
22+
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
2323
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
2424
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
2525
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
@@ -33,7 +33,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo
3333
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
3434
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
3535
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
36-
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
36+
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
3737
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
3838
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
3939
// Check flags in Credential transactions

src/libxrpl/basics/MallocTrim.cpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#include <xrpl/basics/Log.h>
2+
#include <xrpl/basics/MallocTrim.h>
3+
4+
#include <boost/predef.h>
5+
6+
#include <chrono>
7+
#include <cstdint>
8+
#include <cstdio>
9+
#include <fstream>
10+
#include <sstream>
11+
12+
#if defined(__GLIBC__) && BOOST_OS_LINUX
13+
#include <sys/resource.h>
14+
15+
#include <malloc.h>
16+
#include <unistd.h>
17+
18+
// Require RUSAGE_THREAD for thread-scoped page fault tracking
19+
#ifndef RUSAGE_THREAD
20+
#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc"
21+
#endif
22+
23+
namespace {
24+
25+
bool
26+
getRusageThread(struct rusage& ru)
27+
{
28+
return ::getrusage(RUSAGE_THREAD, &ru) == 0; // LCOV_EXCL_LINE
29+
}
30+
31+
} // namespace
32+
#endif
33+
34+
namespace xrpl {
35+
36+
namespace detail {
37+
38+
// cSpell:ignore statm
39+
40+
#if defined(__GLIBC__) && BOOST_OS_LINUX
41+
42+
inline int
43+
mallocTrimWithPad(std::size_t padBytes)
44+
{
45+
return ::malloc_trim(padBytes);
46+
}
47+
48+
long
49+
parseStatmRSSkB(std::string const& statm)
50+
{
51+
// /proc/self/statm format: size resident shared text lib data dt
52+
// We want the second field (resident) which is in pages
53+
std::istringstream iss(statm);
54+
long size, resident;
55+
if (!(iss >> size >> resident))
56+
return -1;
57+
58+
// Convert pages to KB
59+
long const pageSize = ::sysconf(_SC_PAGESIZE);
60+
if (pageSize <= 0)
61+
return -1;
62+
63+
return (resident * pageSize) / 1024;
64+
}
65+
66+
#endif // __GLIBC__ && BOOST_OS_LINUX
67+
68+
} // namespace detail
69+
70+
MallocTrimReport
71+
mallocTrim(std::string_view tag, beast::Journal journal)
72+
{
73+
// LCOV_EXCL_START
74+
75+
MallocTrimReport report;
76+
77+
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
78+
JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")";
79+
#else
80+
// Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB
81+
// showed no clear, consistent benefit from custom padding—0 provided the best overall balance
82+
// of RSS reduction and trim-latency stability without adding a tuning surface.
83+
constexpr std::size_t TRIM_PAD = 0;
84+
85+
report.supported = true;
86+
87+
if (journal.debug())
88+
{
89+
auto readFile = [](std::string const& path) -> std::string {
90+
std::ifstream ifs(path, std::ios::in | std::ios::binary);
91+
if (!ifs.is_open())
92+
return {};
93+
94+
// /proc files are often not seekable; read as a stream.
95+
std::ostringstream oss;
96+
oss << ifs.rdbuf();
97+
return oss.str();
98+
};
99+
100+
std::string const tagStr{tag};
101+
std::string const statmPath = "/proc/self/statm";
102+
103+
auto const statmBefore = readFile(statmPath);
104+
long const rssBeforeKB = detail::parseStatmRSSkB(statmBefore);
105+
106+
struct rusage ru0{};
107+
bool const have_ru0 = getRusageThread(ru0);
108+
109+
auto const t0 = std::chrono::steady_clock::now();
110+
111+
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
112+
113+
auto const t1 = std::chrono::steady_clock::now();
114+
115+
struct rusage ru1{};
116+
bool const have_ru1 = getRusageThread(ru1);
117+
118+
auto const statmAfter = readFile(statmPath);
119+
long const rssAfterKB = detail::parseStatmRSSkB(statmAfter);
120+
121+
// Populate report fields
122+
report.rssBeforeKB = rssBeforeKB;
123+
report.rssAfterKB = rssAfterKB;
124+
report.durationUs = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0);
125+
126+
if (have_ru0 && have_ru1)
127+
{
128+
report.minfltDelta = ru1.ru_minflt - ru0.ru_minflt;
129+
report.majfltDelta = ru1.ru_majflt - ru0.ru_majflt;
130+
}
131+
132+
std::int64_t const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0)
133+
? 0
134+
: (static_cast<std::int64_t>(rssAfterKB) - static_cast<std::int64_t>(rssBeforeKB));
135+
136+
JLOG(journal.debug()) << "malloc_trim tag=" << tagStr << " result=" << report.trimResult
137+
<< " pad=" << TRIM_PAD << " bytes"
138+
<< " rss_before=" << rssBeforeKB << "kB"
139+
<< " rss_after=" << rssAfterKB << "kB"
140+
<< " delta=" << deltaKB << "kB"
141+
<< " duration_us=" << report.durationUs.count()
142+
<< " minflt_delta=" << report.minfltDelta
143+
<< " majflt_delta=" << report.majfltDelta;
144+
}
145+
else
146+
{
147+
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
148+
}
149+
150+
#endif
151+
152+
return report;
153+
154+
// LCOV_EXCL_STOP
155+
}
156+
157+
} // namespace xrpl

0 commit comments

Comments
 (0)