From bc937d38a2f0ea841f1884104e5db521365eab1a Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 16 Apr 2022 11:36:58 +0800 Subject: [PATCH] Allow to set "working set limit" on non-Windows OS PR #16874. --- src/app/application.cpp | 41 +++++++++++++------- src/app/application.h | 6 --- src/base/interfaces/iapplication.h | 4 -- src/gui/advancedsettings.cpp | 24 ++++++------ src/gui/advancedsettings.h | 3 +- src/webui/api/appcontroller.cpp | 6 +++ src/webui/webapplication.h | 2 +- src/webui/www/private/views/preferences.html | 10 +++++ 8 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index 7247ca1f0..f7f48a158 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -39,6 +39,8 @@ #include #include #include +#elif defined(Q_OS_UNIX) +#include #endif #include @@ -127,9 +129,7 @@ Application::Application(int &argc, char **argv) , m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_qs)) , m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_qs)) , m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_qs)) -#ifdef Q_OS_WIN , m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_qs)) -#endif { qRegisterMetaType("Log::Msg"); qRegisterMetaType("Log::Peer"); @@ -206,7 +206,6 @@ const QBtCommandLineParameters &Application::commandLineArgs() const return m_commandLineArgs; } -#ifdef Q_OS_WIN int Application::memoryWorkingSetLimit() const { return m_storeMemoryWorkingSetLimit.get(512); @@ -220,7 +219,6 @@ void Application::setMemoryWorkingSetLimit(const int size) m_storeMemoryWorkingSetLimit = size; applyMemoryWorkingSetLimit(); } -#endif bool Application::isFileLoggerEnabled() const { @@ -598,9 +596,7 @@ void Application::processParams(const QStringList ¶ms) int Application::exec(const QStringList ¶ms) { -#ifdef Q_OS_WIN applyMemoryWorkingSetLimit(); -#endif Net::ProxyConfigurationManager::initInstance(); Net::DownloadManager::initInstance(); @@ -771,28 +767,43 @@ void Application::shutdownCleanup(QSessionManager &manager) } #endif -#ifdef Q_OS_WIN void Application::applyMemoryWorkingSetLimit() { - const SIZE_T UNIT_SIZE = 1024 * 1024; // MiB - const SIZE_T maxSize = memoryWorkingSetLimit() * UNIT_SIZE; - const SIZE_T minSize = std::min((64 * UNIT_SIZE), (maxSize / 2)); + const size_t MiB = 1024 * 1024; + const QString logMessage = tr("Failed to set physical memory (RAM) usage limit. Error code: %1. Error message: \"%2\""); + +#ifdef Q_OS_WIN + const SIZE_T maxSize = memoryWorkingSetLimit() * MiB; + const SIZE_T minSize = std::min((64 * MiB), (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)) + const DWORD msgLength = ::FormatMessageW((FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS) + , nullptr, errorCode, LANG_USER_DEFAULT, reinterpret_cast(&lpMsgBuf), 0, nullptr); + if (msgLength > 0) { 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); + LogMsg(logMessage.arg(QString::number(errorCode), message), Log::WARNING); + } +#elif defined(Q_OS_UNIX) + // has no effect on linux but it might be meaningful for other OS + rlimit limit {}; + + if (::getrlimit(RLIMIT_RSS, &limit) != 0) + return; + + limit.rlim_cur = memoryWorkingSetLimit() * MiB; + if (::setrlimit(RLIMIT_RSS, &limit) != 0) + { + const auto message = QString::fromLocal8Bit(strerror(errno)); + LogMsg(logMessage.arg(QString::number(errno), message), Log::WARNING); } -} #endif +} void Application::cleanup() { diff --git a/src/app/application.h b/src/app/application.h index d44ed9ab2..5747b01e5 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -113,10 +113,8 @@ public: int fileLoggerAgeType() const override; void setFileLoggerAgeType(int value) override; -#ifdef Q_OS_WIN int memoryWorkingSetLimit() const override; void setMemoryWorkingSetLimit(int size) override; -#endif #ifndef DISABLE_GUI QPointer mainWindow() override; @@ -137,9 +135,7 @@ private: void processParams(const QStringList ¶ms); void runExternalProgram(const BitTorrent::Torrent *torrent) const; void sendNotificationEmail(const BitTorrent::Torrent *torrent); -#ifdef Q_OS_WIN void applyMemoryWorkingSetLimit(); -#endif #ifndef DISABLE_GUI #ifdef Q_OS_MACOS @@ -166,9 +162,7 @@ private: SettingValue m_storeFileLoggerAge; SettingValue m_storeFileLoggerAgeType; SettingValue m_storeFileLoggerPath; -#ifdef Q_OS_WIN SettingValue m_storeMemoryWorkingSetLimit; -#endif #ifndef DISABLE_GUI QPointer m_window; diff --git a/src/base/interfaces/iapplication.h b/src/base/interfaces/iapplication.h index 440290c2c..94cdbdb43 100644 --- a/src/base/interfaces/iapplication.h +++ b/src/base/interfaces/iapplication.h @@ -30,8 +30,6 @@ #pragma once -#include - class QString; class Path; @@ -58,8 +56,6 @@ public: virtual int fileLoggerAgeType() const = 0; virtual void setFileLoggerAgeType(int value) = 0; -#ifdef Q_OS_WIN virtual int memoryWorkingSetLimit() const = 0; virtual void setMemoryWorkingSetLimit(int size) = 0; -#endif }; diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 01438c1db..5121e0e46 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -62,9 +62,9 @@ namespace // qBittorrent section QBITTORRENT_HEADER, RESUME_DATA_STORAGE, + MEMORY_WORKING_SET_LIMIT, #if defined(Q_OS_WIN) OS_MEMORY_PRIORITY, - MEMORY_WORKING_SET_LIMIT, #endif // network interface NETWORK_IFACE, @@ -177,6 +177,8 @@ void AdvancedSettings::saveAdvancedSettings() ? BitTorrent::ResumeDataStorageType::Legacy : BitTorrent::ResumeDataStorageType::SQLite); + // Physical memory (RAM) usage limit + dynamic_cast(QCoreApplication::instance())->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value()); #if defined(Q_OS_WIN) BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal; switch (m_comboBoxOSMemoryPriority.currentIndex()) @@ -199,8 +201,6 @@ void AdvancedSettings::saveAdvancedSettings() break; } session->setOSMemoryPriority(prio); - - dynamic_cast(QCoreApplication::instance())->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value()); #endif // Async IO threads session->setAsyncIOThreads(m_spinBoxAsyncIOThreads.value()); @@ -419,6 +419,14 @@ void AdvancedSettings::loadAdvancedSettings() m_comboBoxResumeDataStorage.setCurrentIndex((session->resumeDataStorageType() == BitTorrent::ResumeDataStorageType::Legacy) ? 0 : 1); addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage); + // Physical memory (RAM) usage limit + m_spinBoxMemoryWorkingSetLimit.setMinimum(1); + m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits::max()); + m_spinBoxMemoryWorkingSetLimit.setSuffix(tr(" MiB")); + m_spinBoxMemoryWorkingSetLimit.setToolTip(tr("This option is less effective on Linux")); + m_spinBoxMemoryWorkingSetLimit.setValue(dynamic_cast(QCoreApplication::instance())->memoryWorkingSetLimit()); + addRow(MEMORY_WORKING_SET_LIMIT, (tr("Physical memory (RAM) usage limit") + u' ' + makeLink(u"https://wikipedia.org/wiki/Working_set", u"(?)")) + , &m_spinBoxMemoryWorkingSetLimit); #if defined(Q_OS_WIN) m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")}); int OSMemoryPriorityIndex = 0; @@ -445,17 +453,7 @@ void AdvancedSettings::loadAdvancedSettings() addRow(OS_MEMORY_PRIORITY, (tr("Process memory priority (Windows >= 8 only)") + u' ' + makeLink(u"https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-memory_priority_information", u"(?)")) , &m_comboBoxOSMemoryPriority); - - m_spinBoxMemoryWorkingSetLimit.setMinimum(1); - m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits::max()); - m_spinBoxMemoryWorkingSetLimit.setSuffix(tr(" MiB")); - m_spinBoxMemoryWorkingSetLimit.setValue(dynamic_cast(QCoreApplication::instance())->memoryWorkingSetLimit()); - - addRow(MEMORY_WORKING_SET_LIMIT, (tr("Physical memory (RAM) usage limit") - + u' ' + makeLink(u"https://wikipedia.org/wiki/Working_set", u"(?)")) - , &m_spinBoxMemoryWorkingSetLimit); #endif - // Async IO threads m_spinBoxAsyncIOThreads.setMinimum(1); m_spinBoxAsyncIOThreads.setMaximum(1024); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index 32676aa72..34f48b672 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -58,7 +58,7 @@ private: void loadAdvancedSettings(); template void addRow(int row, const QString &text, T *widget); - QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage, m_spinBoxDiskQueueSize, + QSpinBox m_spinBoxMemoryWorkingSetLimit, m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage, m_spinBoxDiskQueueSize, m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS, m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark, m_spinBoxSendBufferWatermarkFactor, m_spinBoxConnectionSpeed, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout, @@ -82,7 +82,6 @@ private: // OS dependent settings #if defined(Q_OS_WIN) QComboBox m_comboBoxOSMemoryPriority; - QSpinBox m_spinBoxMemoryWorkingSetLimit; #endif #ifndef Q_OS_MACOS diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index fee2fea95..79b44496a 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -45,6 +45,7 @@ #include "base/bittorrent/session.h" #include "base/global.h" +#include "base/interfaces/iapplication.h" #include "base/net/portforwarder.h" #include "base/net/proxyconfigurationmanager.h" #include "base/path.h" @@ -285,6 +286,8 @@ void AppController::preferencesAction() // Advanced settings // qBitorrent preferences + // Physical memory (RAM) usage limit + data[u"memory_working_set_limit"_qs] = dynamic_cast(QCoreApplication::instance())->memoryWorkingSetLimit(); // Current network interface data[u"current_network_interface"_qs] = session->networkInterface(); // Current network interface address @@ -738,6 +741,9 @@ void AppController::setPreferencesAction() // Advanced settings // qBittorrent preferences + // Physical memory (RAM) usage limit + if (hasKey(u"memory_working_set_limit"_qs)) + dynamic_cast(QCoreApplication::instance())->setMemoryWorkingSetLimit(it.value().toInt()); // Current network interface if (hasKey(u"current_network_interface"_qs)) { diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 70e563460..d56578141 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -48,7 +48,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version API_VERSION {2, 8, 9}; +inline const Utils::Version API_VERSION {2, 8, 10}; class APIController; class AuthController; diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index db06eafa9..856a7cb47 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -903,6 +903,14 @@
QBT_TR(qBittorrent Section)QBT_TR[CONTEXT=OptionsDialog] (QBT_TR(Open documentation)QBT_TR[CONTEXT=HttpServer]) + + + +
+ + +   QBT_TR(MiB)QBT_TR[CONTEXT=OptionsDialog] +
@@ -1947,6 +1955,7 @@ // Advanced settings // qBittorrent section + $('memoryWorkingSetLimit').setProperty('value', pref.memory_working_set_limit); updateNetworkInterfaces(pref.current_network_interface); updateInterfaceAddresses(pref.current_network_interface, pref.current_interface_address); $('saveResumeDataInterval').setProperty('value', pref.save_resume_data_interval); @@ -2346,6 +2355,7 @@ // Update advanced settings // qBittorrent section + settings.set('memory_working_set_limit', $('memoryWorkingSetLimit').getProperty('value')); settings.set('current_network_interface', $('networkInterface').getProperty('value')); settings.set('current_interface_address', $('optionalIPAddressToBind').getProperty('value')); settings.set('save_resume_data_interval', $('saveResumeDataInterval').getProperty('value'));