From 299f981441292b640459ae10fd344738bc920021 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Tue, 1 Mar 2022 16:42:25 +0300 Subject: [PATCH] Allow to limit max memory working set size PR #16485. --- src/app/application.cpp | 47 ++++++++++++++++++++++++++++++++++++ src/app/application.h | 11 +++++++++ src/gui/advancedsettings.cpp | 12 +++++++++ src/gui/advancedsettings.h | 1 + 4 files changed, 71 insertions(+) diff --git a/src/app/application.cpp b/src/app/application.cpp index a92e4b6ca..262961dec 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -64,6 +64,7 @@ #include "base/bittorrent/session.h" #include "base/bittorrent/torrent.h" #include "base/exceptions.h" +#include "base/global.h" #include "base/iconprovider.h" #include "base/logger.h" #include "base/net/downloadmanager.h" @@ -121,6 +122,9 @@ Application::Application(int &argc, char **argv) , m_running(false) , m_shutdownAct(ShutdownDialogAction::Exit) , m_commandLineArgs(parseCommandLine(this->arguments())) +#ifdef Q_OS_WIN + , m_storeMemoryWorkingSetLimit(SETTINGS_KEY("MemoryWorkingSetLimit")) +#endif , m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY("Enabled")) , m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY("Backup")) , m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY("DeleteOld")) @@ -204,6 +208,22 @@ const QBtCommandLineParameters &Application::commandLineArgs() const return m_commandLineArgs; } +#ifdef Q_OS_WIN +int Application::memoryWorkingSetLimit() const +{ + return m_storeMemoryWorkingSetLimit.get(512); +} + +void Application::setMemoryWorkingSetLimit(const int size) +{ + if (size == memoryWorkingSetLimit()) + return; + + m_storeMemoryWorkingSetLimit = size; + applyMemoryWorkingSetLimit(); +} +#endif + bool Application::isFileLoggerEnabled() const { return m_storeFileLoggerEnabled.get(true); @@ -602,6 +622,10 @@ void Application::processParams(const QStringList ¶ms) int Application::exec(const QStringList ¶ms) { +#ifdef Q_OS_WIN + applyMemoryWorkingSetLimit(); +#endif + Net::ProxyConfigurationManager::initInstance(); Net::DownloadManager::initInstance(); IconProvider::initInstance(); @@ -771,6 +795,29 @@ void Application::shutdownCleanup(QSessionManager &manager) } #endif +#ifdef Q_OS_WIN +void Application::applyMemoryWorkingSetLimit() +{ + const int UNIT_SIZE = 1024 * 1024; // MiB + const SIZE_T maxSize = memoryWorkingSetLimit() * UNIT_SIZE; + const SIZE_T minSize = std::min((64 * UNIT_SIZE), (maxSize / 2)); + if (!::SetProcessWorkingSetSizeEx(::GetCurrentProcess(), minSize, maxSize, QUOTA_LIMITS_HARDWS_MAX_ENABLE)) + { + const DWORD errorCode = ::GetLastError(); + QString message; + LPVOID lpMsgBuf = nullptr; + if (::FormatMessageW((FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS) + , nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&lpMsgBuf), 0, nullptr)) + { + message = QString::fromWCharArray(reinterpret_cast(lpMsgBuf)).trimmed(); + ::LocalFree(lpMsgBuf); + } + LogMsg(tr("Failed to set physical memory (RAM) usage limit. Error code: %1. Error message: \"%2\"") + .arg(QString::number(errorCode), message), Log::WARNING); + } +} +#endif + void Application::cleanup() { // cleanup() can be called multiple times during shutdown. We only need it once. diff --git a/src/app/application.h b/src/app/application.h index 9980e80b7..1edf83e3f 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -89,6 +89,11 @@ public: const QBtCommandLineParameters &commandLineArgs() const; +#ifdef Q_OS_WIN + int memoryWorkingSetLimit() const; + void setMemoryWorkingSetLimit(int size); +#endif + // FileLogger properties bool isFileLoggerEnabled() const; void setFileLoggerEnabled(bool value); @@ -122,6 +127,9 @@ private slots: #endif private: +#ifdef Q_OS_WIN + void applyMemoryWorkingSetLimit(); +#endif void initializeTranslation(); void processParams(const QStringList ¶ms); void runExternalProgram(const BitTorrent::Torrent *torrent) const; @@ -147,6 +155,9 @@ private: QTranslator m_translator; QStringList m_paramsQueue; +#ifdef Q_OS_WIN + SettingValue m_storeMemoryWorkingSetLimit; +#endif SettingValue m_storeFileLoggerEnabled; SettingValue m_storeFileLoggerBackup; SettingValue m_storeFileLoggerDeleteOld; diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 8df517624..1474ccb5c 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -64,6 +64,7 @@ namespace RESUME_DATA_STORAGE, #if defined(Q_OS_WIN) OS_MEMORY_PRIORITY, + MEMORY_WORKING_SET_LIMIT, #endif // network interface NETWORK_IFACE, @@ -198,6 +199,8 @@ void AdvancedSettings::saveAdvancedSettings() break; } session->setOSMemoryPriority(prio); + + static_cast(QCoreApplication::instance())->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value()); #endif // Async IO threads session->setAsyncIOThreads(m_spinBoxAsyncIOThreads.value()); @@ -442,6 +445,15 @@ void AdvancedSettings::loadAdvancedSettings() addRow(OS_MEMORY_PRIORITY, (tr("Process memory priority (Windows >= 8 only)") + ' ' + makeLink("https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-memory_priority_information", "(?)")) , &m_comboBoxOSMemoryPriority); + + m_spinBoxMemoryWorkingSetLimit.setMinimum(1); + m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits::max()); + m_spinBoxMemoryWorkingSetLimit.setSuffix(tr(" MiB")); + m_spinBoxMemoryWorkingSetLimit.setValue(static_cast(QCoreApplication::instance())->memoryWorkingSetLimit()); + + addRow(MEMORY_WORKING_SET_LIMIT, (tr("Physical memory (RAM) usage limit") + + ' ' + makeLink("https://wikipedia.org/wiki/Working_set", "(?)")) + , &m_spinBoxMemoryWorkingSetLimit); #endif // Async IO threads diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index bb93fcb4c..32676aa72 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -82,6 +82,7 @@ private: // OS dependent settings #if defined(Q_OS_WIN) QComboBox m_comboBoxOSMemoryPriority; + QSpinBox m_spinBoxMemoryWorkingSetLimit; #endif #ifndef Q_OS_MACOS