#include "TempFileManager.h" #include #include #include #include #include #include "ServerLogger.h" class TempFileManager::Impl { public: struct FileControlBlock { WebFileInfo info; std::chrono::steady_clock::time_point expire_time; std::chrono::steady_clock::time_point active_start_time; std::chrono::seconds remaining_timeout; std::chrono::seconds max_processing_timeout; bool is_active = false; bool is_permanent = false; // 是否永久保留 bool is_invalid = false; }; std::string base_dir; std::unordered_map file_map; mutable std::shared_mutex rw_mutex; // C++20 jthread std::jthread cleanup_thread; public: Impl() noexcept { } ~Impl() noexcept { // 1. 必须第一步:发送停止信号并阻塞等待后台清理线程彻底退出 // 这样能确保后面遍历 file_map 时,绝对没有第二个线程在并发访问它 SCLOG_INFO("TempFileManager Destruction Begin"); cleanup_thread.request_stop(); if (cleanup_thread.joinable()) cleanup_thread.join(); // 2. 此时属于单线程环境,无需加锁。遍历并销毁所有非永久文件 std::error_code ec; for (const auto& [_, fcb] : file_map) { if (!fcb.is_permanent && !base_dir.empty()) { std::string path = fcb.info.MakePath(base_dir); // 使用无异常重载版本,即使磁盘物理删除失败(如文件被外层强行独占锁死)也绝不抛出异常 std::filesystem::remove(path, ec); if (!ec) SCLOGF_INFO("Temp File {} Deleted.", path); else SCLOGF_WARNING("Temp File {} Delete Failed: {}({})", path, ec.value(), ec.message()); } // 如果 is_permanent 为 true(永久化文件),则跳过不处理,物理文件将安全留在磁盘上 } // 3. 析构结束,file_map 内存控制块会自动退栈销毁 SCLOG_INFO("TempFileManager Destruction Finished"); } public: void CleanupLoop(std::stop_token st) noexcept { while (!st.stop_requested()) { // 改进:引入分段休眠(10次*100ms),让析构函数调用 join() 时能在最大 100ms 内瞬间响应退出 // 避免传统的 sleep_for(1s) 导致服务器内核关闭时卡顿 1 秒 for (int i = 0; i < 10; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (st.stop_requested()) return; // 随时收到终止信号随时退出 } std::unique_lock lock(rw_mutex); if (base_dir.empty()) continue; auto now = std::chrono::steady_clock::now(); for (auto it = file_map.begin(); it != file_map.end(); ) { bool should_delete = false; if (it->second.is_invalid) should_delete = true; //手动设置的无条件立即删除 if (!it->second.is_permanent) { if (it->second.is_active) { if ((it->second.max_processing_timeout > std::chrono::seconds(0)) && ((now - it->second.active_start_time) > it->second.max_processing_timeout)) should_delete = true; } else { if (now > it->second.expire_time) should_delete = true; } } if (should_delete) { std::error_code ec; std::string path = it->second.info.MakePath(base_dir); std::filesystem::remove(path, ec); if (!ec) SCLOGF_INFO("Temp File {} {}, Deleted.", path, (it->second.is_invalid ? "Invalid" : "Expried")); else SCLOGF_WARNING("Temp File {} {}, Delete Failed: {}({})", path, (it->second.is_invalid ? "Invalid" : "Expried"), ec.value(), ec.message()); it = file_map.erase(it); } else ++it; } } } }; TempFileManager::TempFileManager() noexcept : pimpl(std::make_unique()) { } TempFileManager::~TempFileManager() noexcept = default; // ================= 以下其余基础业务接口保持不变 ================= bool TempFileManager::SetBaseDirectory(std::string dir) noexcept { std::unique_lock lock(pimpl->rw_mutex); // 加上写锁,防止与其他文件操作并发 pimpl->base_dir = std::move(dir); std::error_code ec; // 物理创建目录(无异常版本) std::filesystem::create_directories(pimpl->base_dir, ec); if (ec) SCLOGF_ERROR("TempFileManager: create_directories error: {}({})", ec.value(), ec.message()); // 只有在线程未启动时才启动后台清理线程,确保整个生命周期只启动一次 if (!pimpl->cleanup_thread.joinable() && !ec) { pimpl->cleanup_thread = std::jthread([this] (std::stop_token st) { this->pimpl->CleanupLoop(st); }); SCLOGF_INFO("TempFileManager: Thread Started."); } else SCLOGF_ERROR("TempFileManager: Failed To Start Thread (Thread Joinable: {}, Error: {})", pimpl->cleanup_thread.joinable(), ec.message()); return !ec; } bool TempFileManager::RegisterFile(const WebFileInfo& info, std::chrono::seconds timeout, std::chrono::seconds max_proc_timeout) noexcept { try { std::unique_lock lock(pimpl->rw_mutex); if (pimpl->file_map.contains(info.GetStorageFileName())) { SCLOGF_WARNING("RegisterFile Failed: File {} Already Exists", info.GetStorageFileName()); return false; } Impl::FileControlBlock fcb { .info = info, .expire_time = std::chrono::steady_clock::now() + timeout, .remaining_timeout = timeout, .max_processing_timeout = max_proc_timeout }; pimpl->file_map[info.GetStorageFileName()] = std::move(fcb); SCLOGF_INFO("File {}({}) Registered", info.GetOriginalFileName(), info.GetStorageFileName()); return true; } catch (...) { SCLOG_WARNING("RegisterFile Failed: Exception"); return false; } } bool TempFileManager::InvalidateFile(const WebFileInfo& info) { std::unique_lock lock(pimpl->rw_mutex); if (pimpl->file_map.contains(info.GetStorageFileName())) { pimpl->file_map[info.GetStorageFileName()].is_invalid = true; SCLOGF_INFO("File [{}]({}) Marked AS Invalid", info.GetStorageFileName(), info.GetOriginalFileName()); return true; } else SCLOGF_WARNING("InvalidateFile({}/{}) Failed: Not Found", info.GetOriginalFileName(), info.GetStorageFileName()); return false; } bool TempFileManager::InvalidateFile(const std::map& info) { size_t succ_cnt = 0; std::unique_lock lock(pimpl->rw_mutex); for (const auto& [on, sn] : info) { if (pimpl->file_map.contains(sn)) { pimpl->file_map[sn].is_invalid = true; succ_cnt++; SCLOGF_INFO("File [{}]({}) Marked AS Invalid", sn, on); } else SCLOGF_WARNING("InvalidateFile({}/{}) Failed: Not Found", on, sn); } return (succ_cnt == info.size()); } bool TempFileManager::CopyFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept { std::shared_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); auto dest_file = std::filesystem::path(dest_absolute_path) / storage_name; if (it == pimpl->file_map.end()) { SCLOGF_WARNING("CopyFileTo({}, {}) Failed: File Not Exists", storage_name, dest_file); return false; } std::error_code ec; std::filesystem::copy(it->second.info.MakePath(pimpl->base_dir), dest_file, std::filesystem::copy_options::overwrite_existing, ec); if (ec) SCLOGF_WARNING("CopyFileTo({}, {}) Failed: {}({})", storage_name, dest_file, ec.value(), ec.message()); else SCLOGF_INFO("CopyFileTo({}, {}) Success", storage_name, dest_file); return !ec; } bool TempFileManager::CutFileTo(const std::string& storage_name, const std::string& dest_absolute_path) noexcept { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); auto dest_file = std::filesystem::path(dest_absolute_path) / storage_name; if (it == pimpl->file_map.end()) { SCLOGF_WARNING("CutFileTo({}, {}) Failed: File Not Exists", storage_name, dest_file); return false; } std::error_code ec; std::filesystem::rename(it->second.info.MakePath(pimpl->base_dir), dest_file, ec); if (ec) SCLOGF_WARNING("CutFileTo({}, {}) Failed: {}({})", storage_name, dest_file, ec.value(), ec.message()); else SCLOGF_INFO("CutFileTo({}, {}) Success", storage_name, dest_file); if (ec) return false; pimpl->file_map.erase(it); return true; } bool TempFileManager::RenameFile(const std::string& old_storage_name, const std::string& new_storage_name) noexcept { try { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(old_storage_name); if ((it == pimpl->file_map.end()) || pimpl->file_map.contains(new_storage_name)) { SCLOGF_WARNING("RenameFile({}, {}) Failed: File Not Exists", old_storage_name, new_storage_name); return false; } std::error_code ec; std::string old_path = it->second.info.MakePath(pimpl->base_dir); Impl::FileControlBlock fcb = std::move(it->second); fcb.info.SetStorageFileName(new_storage_name); std::string new_path = fcb.info.MakePath(pimpl->base_dir); std::filesystem::rename(old_path, new_path, ec); if (ec) SCLOGF_WARNING("RenameFile({}, {}) Failed: {}({})", old_storage_name, new_storage_name, ec.value(), ec.message()); else SCLOGF_INFO("RenameFile({}, {}) Success", old_storage_name, new_storage_name); if (ec) return false; pimpl->file_map.erase(it); pimpl->file_map[new_storage_name] = std::move(fcb); return true; } catch (...) { SCLOG_WARNING("RenameFile Failed: Exception"); return false; } } bool TempFileManager::DeleteFile(const std::string& storage_name) noexcept { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); if (it == pimpl->file_map.end()) { SCLOGF_WARNING("DeleteFile({}) Failed: File Not Exists", storage_name); return false; } std::error_code ec; std::filesystem::remove(it->second.info.MakePath(pimpl->base_dir), ec); if (ec) SCLOGF_WARNING("DeleteFile({}) Failed: {}({})", storage_name, ec.value(), ec.message()); else SCLOGF_INFO("DeleteFile({}) Success", storage_name); if (ec) return false; pimpl->file_map.erase(it); return true; } bool TempFileManager::ActiveFile(const std::string& storage_name) noexcept { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); if ((it == pimpl->file_map.end()) || it->second.is_active) { SCLOGF_WARNING("ActiveFile({}) Failed: {}", storage_name, (it->second.is_active ? "Already Actived" : "File Not Exists")); return false; } auto now = std::chrono::steady_clock::now(); if (it->second.expire_time > now) it->second.remaining_timeout = std::chrono::duration_cast(it->second.expire_time - now); else it->second.remaining_timeout = std::chrono::seconds(0); it->second.is_active = true; it->second.active_start_time = now; SCLOGF_INFO("ActiveFile({}) Success", storage_name); return true; } bool TempFileManager::DeactiveFile(const std::string& storage_name) noexcept { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); if ((it == pimpl->file_map.end()) || !it->second.is_active) { SCLOGF_WARNING("DeactiveFile({}) Failed: {}", storage_name, ((it != pimpl->file_map.end()) ? "Already Deactived" : "File Not Exists")); return false; } it->second.is_active = false; it->second.expire_time = std::chrono::steady_clock::now() + it->second.remaining_timeout; SCLOGF_INFO("DeactiveFile({}) Success", storage_name); return true; } bool TempFileManager::SetPermanent(const std::string& storage_name, bool permanent) noexcept { std::unique_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); if (it == pimpl->file_map.end()) { SCLOGF_WARNING("SetPermanent({}) Failed: File Not Exists", storage_name); return false; } it->second.is_permanent = permanent; SCLOGF_INFO("SetPermanent({}) Success", storage_name); return true; } bool TempFileManager::FileExists(const std::string& storage_name) const noexcept { std::shared_lock lock(pimpl->rw_mutex); return pimpl->file_map.contains(storage_name); } bool TempFileManager::GetFileInfo(const std::string& storage_name, WebFileInfo& out_info) const noexcept { try { std::shared_lock lock(pimpl->rw_mutex); auto it = pimpl->file_map.find(storage_name); if (it == pimpl->file_map.end()) return false; out_info = it->second.info; return true; } catch (...) { SCLOGF_WARNING("GetFileInfo({}) Failed: Exception", storage_name); return false; } } size_t TempFileManager::GetFileCount() const noexcept { return pimpl->file_map.size(); } std::vector TempFileManager::GetAllFileInfos() const noexcept { std::vector list; try { std::shared_lock lock(pimpl->rw_mutex); // 申请读锁,支持高并发并发读取 list.reserve(pimpl->file_map.size()); // 提前预留空间,减少内存重分配次数 for (const auto& [_, fcb] : pimpl->file_map) list.push_back(fcb.info); // 拷贝文件元数据到外部 } catch (...) { list.clear(); // 极端内存崩溃(bad_alloc)时,清空并安全返回空数组 } return list; }