From 5a3b4e50cd2411502949f49df85324c6906b145b Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 16 Oct 2026 11:48:46 +0300 Subject: [PATCH] Save session data once on shutdown, before waiting for stopped announces Write it after downloads stop, or after the loop if skipped; one error policy for saves. --- src/control.cc | 10 ++++ src/control.h | 2 + src/main.cc | 6 ++- src/session/session_manager.cc | 89 ++++++++++++++++++++++------------ src/session/session_manager.h | 4 ++ 5 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/control.cc b/src/control.cc index 4b35c5a5..5a427f70 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" @@ -152,6 +153,15 @@ Control::handle_shutdown() { m_core->shutdown(true); } + if (!m_shutdown_session_saved) { + // Transfers are stopped, so this is the final state; write it before the + // stopped announces can hold the shutdown. 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_task_shutdown_clear_requests.is_scheduled()) torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_shutdown_clear_requests, 10s); diff --git a/src/control.h b/src/control.h index ceb3656e..47919a52 100644 --- a/src/control.h +++ b/src/control.h @@ -50,6 +50,7 @@ public: bool is_shutdown_completed(); bool is_shutdown_received() { return m_shutdown_received; } bool is_shutdown_started() { return m_shutdown_quick; } + bool is_shutdown_session_saved() { return m_shutdown_session_saved; } void initialize(); void cleanup(); @@ -114,6 +115,7 @@ private: align_cacheline + bool m_shutdown_session_saved{}; std::atomic m_shutdown_received{}; std::atomic m_shutdown_quick{}; }; diff --git a/src/main.cc b/src/main.cc index ad6d9f78..3d2d8a26 100644 --- a/src/main.cc +++ b/src/main.cc @@ -489,7 +489,11 @@ main(int argc, char** argv) { torrent::system::Thread::self()->event_loop(); - control->core()->download_list()->session_save(); + // A quick shutdown with no tracker request pending leaves the loop before + // handle_shutdown() could save. + if (!control->is_shutdown_session_saved()) + control->core()->download_list()->session_save(); + control->cleanup(); } catch (torrent::internal_error& e) { diff --git a/src/session/session_manager.cc b/src/session/session_manager.cc index 39ed41a3..0108cdca 100644 --- a/src/session/session_manager.cc +++ b/src/session/session_manager.cc @@ -429,42 +429,62 @@ 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. +// Callers hold m_mutex. +void +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; + } + + 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; +} + +// 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. +void +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()); + report_storage_error_unsafe(e.what()); + return; + + } 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()); +} + +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); @@ -484,10 +504,17 @@ SessionManager::flush_all_and_wait_unsafe(std::unique_lock& lock) { m_finished_condition.wait(lock); for (auto& request : m_finished_saves) - LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str()); + consume_finished_save_unsafe(request); m_finished_saves.clear(); + // Report and reset what was suppressed since the last error line. The count + // includes saves the session thread consumed while this flush waited. + if (m_ignored_storage_error_count != 0) { + lt_log_print(torrent::LOG_ERRORS, "Storage errors flushing session data: ignored:%u", m_ignored_storage_error_count); + 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 1a9b7a56..17626d57 100644 --- a/src/session/session_manager.h +++ b/src/session/session_manager.h @@ -79,6 +79,7 @@ protected: void cleanup(); void flush_all_pending_builds(); + void flush_shutdown_saves(); private: void callback_pending_builds(); @@ -98,6 +99,9 @@ private: using ProcessingSave = std::pair, SaveRequest>; + void consume_finished_save_unsafe(ProcessingSave& request); + void report_storage_error_unsafe(const std::string& what); + torrent::system::Thread* m_thread; torrent::system::callback_id m_callback_id;