From c46cd403ae72c06087c99359fbe9750cff992fc1 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Fri, 28 Jun 2024 12:41:10 +0400 Subject: [PATCH 1/4] Downloader::close() pauses all downloads Otherwise, creating a Downloader object next time may take very long (or that operation may get stuck) if an active download is being saved to slow media. --- src/aria2.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aria2.cpp b/src/aria2.cpp index e58b2bd67..e3d888ef9 100644 --- a/src/aria2.cpp +++ b/src/aria2.cpp @@ -117,6 +117,9 @@ Aria2::Aria2(): void Aria2::close() { + MethodCall methodCall("aria2.pauseAll", m_secret); + doRequest(methodCall); + saveSession(); shutdown(); } From e8afcbe6aebdbe3c497abde47d39a6b8044eb47d Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Fri, 28 Jun 2024 12:50:56 +0400 Subject: [PATCH 2/4] Downloader::close() is called in destructor --- src/downloader.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/downloader.cpp b/src/downloader.cpp index 25dfc1e95..5193c51de 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -151,11 +151,20 @@ Downloader::Downloader() : /* Destructor */ Downloader::~Downloader() { + close(); } void Downloader::close() { - mp_aria->close(); + if ( mp_aria ) { + try { + mp_aria->close(); + } catch (const std::exception& err) { + std::cerr << "ERROR: Failed to save the downloader state: " + << err.what() << std::endl; + } + mp_aria.reset(); + } } std::vector Downloader::getDownloadIds() const { From 42295c901023c31b33775831e223a8581cf0af46 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Fri, 28 Jun 2024 12:52:53 +0400 Subject: [PATCH 3/4] Defense against non-responsive aria RPC on startup Downloader constructor may get stuck if the check for the aria2c RPC being up gets stuck due to curl_easy_perform() never returning (or, at least, taking longer than I was willing to wait). Currently it may happen, for example, after an application crashes with active downloads being saved to slow media. Then the next creation of a Downloader object will deal with aria2c immediately resuming those downloads and becoming unresponsive as it struggles flushing incoming data to slow storage (or because of some other unfortunate timing of the RPC request being received while it cannot yet be served). --- src/aria2.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/aria2.cpp b/src/aria2.cpp index e3d888ef9..ec05e70cc 100644 --- a/src/aria2.cpp +++ b/src/aria2.cpp @@ -97,20 +97,30 @@ Aria2::Aria2(): curl_easy_setopt(p_curl, CURLOPT_PORT, m_port); curl_easy_setopt(p_curl, CURLOPT_POST, 1L); curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer); + curl_easy_setopt(p_curl, CURLOPT_TIMEOUT_MS, 100); - int watchdog = 50; - while(--watchdog) { + typedef std::chrono::duration Seconds; + + const double MAX_WAITING_TIME_SECONDS = 0.5; + const auto t0 = std::chrono::steady_clock::now(); + bool maxWaitingTimeWasExceeded = false; + + CURLcode res = CURLE_OK; + while ( !maxWaitingTimeWasExceeded ) { sleep(10); curlErrorBuffer[0] = 0; - auto res = curl_easy_perform(p_curl); + res = curl_easy_perform(p_curl); if (res == CURLE_OK) { break; - } else if (watchdog == 1) { - LOG_ARIA_ERROR(); } + + const auto dt = std::chrono::steady_clock::now() - t0; + const double elapsedTime = std::chrono::duration_cast(dt).count(); + maxWaitingTimeWasExceeded = elapsedTime > MAX_WAITING_TIME_SECONDS; } curl_easy_cleanup(p_curl); - if (!watchdog) { + if ( maxWaitingTimeWasExceeded ) { + LOG_ARIA_ERROR(); throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd); } } From 65a777d4ed1e9f908d7bfc2da9c822864dbcc03a Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Fri, 28 Jun 2024 13:36:09 +0400 Subject: [PATCH 4/4] Active downloads are paused before starting aria2c The aria session file is edited before starting aria2c. This ensures responsive aria2c RPC server even after a crash. --- src/aria2.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/aria2.cpp b/src/aria2.cpp index ec05e70cc..206a3032e 100644 --- a/src/aria2.cpp +++ b/src/aria2.cpp @@ -4,6 +4,7 @@ #include "xmlrpc.h" #include #include +#include #include #include #include @@ -29,6 +30,31 @@ namespace kiwix { +namespace { + +void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath) +{ + std::ifstream inputFile(ariaSessionFilePath); + if ( !inputFile ) + return; + + std::ostringstream ss; + std::string line; + while ( std::getline(inputFile, line) ) { + if ( !startsWith(line, " pause=") ) { + ss << line << "\n"; + } + if ( !line.empty() && line[0] != ' ' && line[0] != '#' ) { + ss << " pause=true\n"; + } + } + + std::ofstream outputFile(ariaSessionFilePath); + outputFile << ss.str(); +} + +} // unnamed namespace + Aria2::Aria2(): mp_aria(nullptr), m_port(42042), @@ -41,6 +67,7 @@ Aria2::Aria2(): std::string rpc_port = "--rpc-listen-port=" + to_string(m_port); std::string download_dir = "--dir=" + getDataDirectory(); std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session"); + pauseAnyActiveDownloads(session_file); std::string session = "--save-session=" + session_file; std::string inputFile = "--input-file=" + session_file; // std::string log_dir = "--log=\"" + logDir + "\""; @@ -127,9 +154,6 @@ Aria2::Aria2(): void Aria2::close() { - MethodCall methodCall("aria2.pauseAll", m_secret); - doRequest(methodCall); - saveSession(); shutdown(); }