From ffaae64d8329a94c048de5ae4417215a5c6efacd Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 16 Oct 2026 11:48:46 +0300 Subject: [PATCH] Flush session saves before stopped announces and report shutdown save errors Persist resume data on the first shutdown pass; consume futures with one error policy. --- src/control.cc | 9 ++++ src/control.h | 1 + src/session/session_manager.cc | 93 ++++++++++++++++++++++------------ src/session/session_manager.h | 4 ++ 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/control.cc b/src/control.cc index 4b35c5a5..7ff57dc1 100644 --- a/src/control.cc +++ b/src/control.cc @@ -10,6 +10,7 @@ #include #include "core/dht_manager.h" +#include "core/download_list.h" #include "core/http_queue.h" #include "core/manager.h" #include "core/view_manager.h" @@ -137,6 +138,14 @@ Control::handle_shutdown() { if (scgi_thread::thread()->is_active()) scgi_thread::thread()->stop_thread_wait(); + if (!m_shutdown_session_saved) { + // Flush the latest state before stopped announces can consume the timeout. + // A shutdown hook may already have queued these resume builds. + m_core->download_list()->session_save(); + session_thread::manager()->flush_shutdown_saves(); + m_shutdown_session_saved = true; + } + if (!m_shutdown_quick) { torrent::runtime::network_manager()->listen_close(); torrent::runtime::shutdown(); diff --git a/src/control.h b/src/control.h index ceb3656e..db9efaba 100644 --- a/src/control.h +++ b/src/control.h @@ -114,6 +114,7 @@ private: align_cacheline + bool m_shutdown_session_saved{}; std::atomic m_shutdown_received{}; std::atomic m_shutdown_quick{}; }; diff --git a/src/session/session_manager.cc b/src/session/session_manager.cc index 2536d133..d5bdfa19 100644 --- a/src/session/session_manager.cc +++ b/src/session/session_manager.cc @@ -405,42 +405,64 @@ SessionManager::process_finished_saves() { if (!m_active) throw torrent::internal_error("SessionManager::process_finished_saves() called while not active."); - for (auto& request : m_finished_saves) { - try { - request.first.get(); - - } catch (torrent::storage_error& e) { - LT_LOG("error saving download : storage error :download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); - - if (m_last_storage_error_message + std::chrono::minutes(5) > torrent::this_thread::cached_time()) { - m_ignored_storage_error_count++; - continue; - } - - lt_log_print(torrent::LOG_ERRORS, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, e.what()); - - m_last_storage_error_message = torrent::this_thread::cached_time(); - m_ignored_storage_error_count = 0; - - continue; - - } catch (torrent::internal_error& e) { - LT_LOG("error saving download : internal error : download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); - throw; - - } catch (...) { - LT_LOG("error saving download : unknown error : download:%p path:%s", request.second.download, request.second.path.c_str()); - throw; - } - - LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); - } + for (auto& request : m_finished_saves) + consume_finished_save_unsafe(request); m_finished_saves.clear(); callback_save_request(); } +// Storage errors share a five-minute log limit and count what they suppress. +// Returns true when this one was suppressed. Callers hold m_mutex. +bool +SessionManager::report_storage_error_unsafe(const std::string& what) { + if (m_last_storage_error_message + std::chrono::minutes(5) > torrent::this_thread::cached_time()) { + m_ignored_storage_error_count++; + return true; + } + + lt_log_print(torrent::LOG_ERRORS, "Storage errors saving session data for download: ignored:%u : %s", m_ignored_storage_error_count, what.c_str()); + + m_last_storage_error_message = torrent::this_thread::cached_time(); + m_ignored_storage_error_count = 0; + return false; +} + +// The one policy for a finished save, on the session thread and in shutdown +// flushes: storage errors go to report_storage_error_unsafe(), internal and +// unknown errors propagate. Callers hold m_mutex. Returns true when a storage +// error was suppressed. +bool +SessionManager::consume_finished_save_unsafe(ProcessingSave& request) { + try { + request.first.get(); + + } catch (torrent::storage_error& e) { + LT_LOG("error saving download : storage error :download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); + return report_storage_error_unsafe(e.what()); + + } catch (torrent::internal_error& e) { + LT_LOG("error saving download : internal error : download:%p path:%s : %s", request.second.download, request.second.path.c_str(), e.what()); + throw; + + } catch (...) { + LT_LOG("error saving download : unknown error : download:%p path:%s", request.second.download, request.second.path.c_str()); + throw; + } + + LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); + return false; +} + +void +SessionManager::flush_shutdown_saves() { + assert(torrent::this_thread::thread() == torrent::main_thread::thread()); + flush_all_pending_builds(); + std::unique_lock lock(m_mutex); + flush_all_and_wait_unsafe(lock); +} + void SessionManager::flush_all_and_wait_unsafe(std::unique_lock& lock) { LT_LOG("flushing all pending saves", 0); @@ -459,11 +481,20 @@ SessionManager::flush_all_and_wait_unsafe(std::unique_lock& lock) { while (!m_processing_saves.empty()) m_finished_condition.wait(lock); + unsigned int suppressed = 0; for (auto& request : m_finished_saves) - LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); + suppressed += consume_finished_save_unsafe(request) ? 1 : 0; m_finished_saves.clear(); + // Report and reset here; a later session-thread pass would otherwise fold + // these into its own count. Name the two counts apart. + if (m_ignored_storage_error_count != 0) { + lt_log_print(torrent::LOG_ERRORS, "Storage errors flushing session data: ignored:%u, earlier ignored:%u", + suppressed, m_ignored_storage_error_count - suppressed); + m_ignored_storage_error_count = 0; + } + LT_LOG("flushed all pending saves", 0); } diff --git a/src/session/session_manager.h b/src/session/session_manager.h index 4c49e647..f9c9274d 100644 --- a/src/session/session_manager.h +++ b/src/session/session_manager.h @@ -73,6 +73,7 @@ protected: void cleanup(); void flush_all_pending_builds(); + void flush_shutdown_saves(); private: void callback_pending_builds(); @@ -92,6 +93,9 @@ private: using ProcessingSave = std::pair, SaveRequest>; + bool consume_finished_save_unsafe(ProcessingSave& request); + bool report_storage_error_unsafe(const std::string& what); + torrent::system::Thread* m_thread; torrent::system::callback_id m_callback_id;