From aed813cc1b2e26b4fd428283c44b32be101dafe9 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 23 Dec 2023 04:54:55 +0000 Subject: [PATCH 1/4] Flush dbcache early if system is under memory pressure No point forcing memory to get pushed out to swap just to cache db changes when we can write the db changes out instead --- configure.ac | 15 +++++++++++++++ src/common/system.cpp | 29 +++++++++++++++++++++++++++++ src/common/system.h | 2 ++ src/validation.cpp | 12 ++++++++++-- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 4f71515873..04dbb0704e 100644 --- a/configure.ac +++ b/configure.ac @@ -1005,6 +1005,21 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [ AC_MSG_RESULT([no])] ) +AC_MSG_CHECKING(for compatible sysinfo call) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ + struct sysinfo info; + int rv = sysinfo(&info); + unsigned long test = info.freeram + info.bufferram + info.mem_unit; + ]])], + [ + AC_MSG_RESULT(yes); + AC_DEFINE(HAVE_LINUX_SYSINFO, 1, [Define this symbol if you have a Linux-compatible sysinfo call]) + ],[ + AC_MSG_RESULT(no) + ] +) + dnl Check for posix_fallocate AC_MSG_CHECKING([for posix_fallocate]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ diff --git a/src/common/system.cpp b/src/common/system.cpp index 1fa53a5f34..72a66f37bb 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -24,6 +24,10 @@ #include #endif +#ifdef HAVE_LINUX_SYSINFO +#include +#endif + #include #include #include @@ -110,3 +114,28 @@ int64_t GetStartupTime() { return nStartupTime; } + +bool SystemNeedsMemoryReleased() +{ + constexpr size_t low_memory_threshold = 10 * 1024 * 1024 /* 10 MB */; +#ifdef WIN32 + MEMORYSTATUSEX mem_status; + mem_status.dwLength = sizeof(mem_status); + if (GlobalMemoryStatusEx(&mem_status)) { + if (mem_status.dwMemoryLoad >= 99) return true; + if (mem_status.ullAvailPhys < low_memory_threshold) return true; + if (mem_status.ullAvailVirtual < low_memory_threshold) return true; + } +#endif +#ifdef HAVE_LINUX_SYSINFO + struct sysinfo sys_info; + if (!sysinfo(&sys_info)) { + // Explicitly 64-bit in case of 32-bit userspace on 64-bit kernel + const uint64_t free_ram = uint64_t(sys_info.freeram) * sys_info.mem_unit; + const uint64_t buffer_ram = uint64_t(sys_info.bufferram) * sys_info.mem_unit; + if (free_ram + buffer_ram < low_memory_threshold) return true; + } +#endif + // NOTE: sysconf(_SC_AVPHYS_PAGES) doesn't account for caches on at least Linux, so not safe to use here + return false; +} diff --git a/src/common/system.h b/src/common/system.h index 83280d46ee..3a31e80cd3 100644 --- a/src/common/system.h +++ b/src/common/system.h @@ -25,6 +25,8 @@ std::string ShellEscape(const std::string& arg); void runCommand(const std::string& strCommand); #endif +bool SystemNeedsMemoryReleased(); + /** * Return the number of cores available on the current system. * @note This does count virtual cores, such as those provided by HyperThreading. diff --git a/src/validation.cpp b/src/validation.cpp index f8e1de55e9..79dfd288a7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -2628,8 +2629,15 @@ bool Chainstate::FlushStateToDisk( } // The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing). bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE; - // The cache is over the limit, we have to write now. - bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL; + bool fCacheCritical = false; + if (mode == FlushStateMode::IF_NEEDED) { + if (cache_state >= CoinsCacheSizeState::CRITICAL) { + // The cache is over the limit, we have to write now. + fCacheCritical = true; + } else if (SystemNeedsMemoryReleased()) { + fCacheCritical = true; + } + } // It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash. bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow > m_last_write + DATABASE_WRITE_INTERVAL; // It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage. From 71eae9faab3c62650b358b7ffbcea3a17153b20d Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 24 Oct 2020 14:16:00 +0000 Subject: [PATCH 2/4] util: Log reasoning when returning true from SystemNeedsMemoryReleased --- src/common/system.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/common/system.cpp b/src/common/system.cpp index 72a66f37bb..9f049e84dd 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -122,9 +122,12 @@ bool SystemNeedsMemoryReleased() MEMORYSTATUSEX mem_status; mem_status.dwLength = sizeof(mem_status); if (GlobalMemoryStatusEx(&mem_status)) { - if (mem_status.dwMemoryLoad >= 99) return true; - if (mem_status.ullAvailPhys < low_memory_threshold) return true; - if (mem_status.ullAvailVirtual < low_memory_threshold) return true; + if (mem_status.dwMemoryLoad >= 99 || + mem_status.ullAvailPhys < low_memory_threshold || + mem_status.ullAvailVirtual < low_memory_threshold) { + LogPrintf("%s: YES: %s%% memory load; %s available physical memory; %s available virtual memory\n", __func__, int(mem_status.dwMemoryLoad), size_t(mem_status.ullAvailPhys), size_t(mem_status.ullAvailVirtual)); + return true; + } } #endif #ifdef HAVE_LINUX_SYSINFO @@ -133,7 +136,10 @@ bool SystemNeedsMemoryReleased() // Explicitly 64-bit in case of 32-bit userspace on 64-bit kernel const uint64_t free_ram = uint64_t(sys_info.freeram) * sys_info.mem_unit; const uint64_t buffer_ram = uint64_t(sys_info.bufferram) * sys_info.mem_unit; - if (free_ram + buffer_ram < low_memory_threshold) return true; + if (free_ram + buffer_ram < low_memory_threshold) { + LogPrintf("%s: YES: %s free RAM + %s buffer RAM\n", __func__, free_ram, buffer_ram); + return true; + } } #endif // NOTE: sysconf(_SC_AVPHYS_PAGES) doesn't account for caches on at least Linux, so not safe to use here From 1a01c955860b9a8fdea01904f11defbb5f41445c Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 24 Oct 2020 14:43:26 +0000 Subject: [PATCH 3/4] Make lowmem threshold configurable --- src/common/system.cpp | 13 +++++++++---- src/common/system.h | 2 ++ src/init.cpp | 8 ++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/common/system.cpp b/src/common/system.cpp index 9f049e84dd..e0e2ce6c3f 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -115,16 +115,21 @@ int64_t GetStartupTime() return nStartupTime; } +size_t g_low_memory_threshold = 10 * 1024 * 1024 /* 10 MB */; + bool SystemNeedsMemoryReleased() { - constexpr size_t low_memory_threshold = 10 * 1024 * 1024 /* 10 MB */; + if (g_low_memory_threshold <= 0) { + // Intentionally bypass other metrics when disabled entirely + return false; + } #ifdef WIN32 MEMORYSTATUSEX mem_status; mem_status.dwLength = sizeof(mem_status); if (GlobalMemoryStatusEx(&mem_status)) { if (mem_status.dwMemoryLoad >= 99 || - mem_status.ullAvailPhys < low_memory_threshold || - mem_status.ullAvailVirtual < low_memory_threshold) { + mem_status.ullAvailPhys < g_low_memory_threshold || + mem_status.ullAvailVirtual < g_low_memory_threshold) { LogPrintf("%s: YES: %s%% memory load; %s available physical memory; %s available virtual memory\n", __func__, int(mem_status.dwMemoryLoad), size_t(mem_status.ullAvailPhys), size_t(mem_status.ullAvailVirtual)); return true; } @@ -136,7 +141,7 @@ bool SystemNeedsMemoryReleased() // Explicitly 64-bit in case of 32-bit userspace on 64-bit kernel const uint64_t free_ram = uint64_t(sys_info.freeram) * sys_info.mem_unit; const uint64_t buffer_ram = uint64_t(sys_info.bufferram) * sys_info.mem_unit; - if (free_ram + buffer_ram < low_memory_threshold) { + if (free_ram + buffer_ram < g_low_memory_threshold) { LogPrintf("%s: YES: %s free RAM + %s buffer RAM\n", __func__, free_ram, buffer_ram); return true; } diff --git a/src/common/system.h b/src/common/system.h index 3a31e80cd3..0ef6a16462 100644 --- a/src/common/system.h +++ b/src/common/system.h @@ -25,6 +25,8 @@ std::string ShellEscape(const std::string& arg); void runCommand(const std::string& strCommand); #endif +extern size_t g_low_memory_threshold; + bool SystemNeedsMemoryReleased(); /** diff --git a/src/init.cpp b/src/init.cpp index 988daefeec..5b1a1df68f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -482,6 +482,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-includeconf=", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-allowignoredconf", strprintf("For backwards compatibility, treat an unused %s file in the datadir as a warning, not an error.", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-loadblock=", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-lowmem=", strprintf("If system available memory falls below MiB, flush caches (0 to disable, default: %s)", g_low_memory_threshold / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxmempool=", strprintf("Keep the transaction memory pool below megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxorphantx=", strprintf("Keep at most unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mempoolexpiry=", strprintf("Do not keep transactions in the mempool longer than hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -1491,6 +1492,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024)); + if (gArgs.IsArgSet("-lowmem")) { + g_low_memory_threshold = gArgs.GetIntArg("-lowmem", 0 /* not used */) * 1024 * 1024; + } + if (g_low_memory_threshold > 0) { + LogPrintf("* Flushing caches if available system memory drops below %s MiB\n", g_low_memory_threshold / 1024 / 1024); + } + for (bool fLoaded = false; !fLoaded && !ShutdownRequested(node);) { node.mempool = std::make_unique(mempool_opts); From 318c79885233f57d69e317163c055aa319336058 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 24 Oct 2020 14:43:51 +0000 Subject: [PATCH 4/4] Disable lowmem flushing in test that needs determinism --- src/test/validation_chainstate_tests.cpp | 3 +++ src/wallet/test/wallet_tests.cpp | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index fe2d2ba592..7cc39cf7df 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include +#include #include #include #include @@ -24,6 +25,8 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup) //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { + g_low_memory_threshold = 0; // disable to get deterministic flushing + ChainstateManager& manager = *Assert(m_node.chainman); CTxMemPool& mempool = *Assert(m_node.mempool); Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 27a81b3669..737463899b 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -783,6 +784,9 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup) //! rescanning where new transactions in new blocks could be lost. BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) { + // FIXME: this test fails for some reason if there's a flush + g_low_memory_threshold = 0; + m_args.ForceSetArg("-unsafesqlitesync", "1"); // Create new wallet with known key and unload it. WalletContext context;