upgrade core add header check and incrace upload speed
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
#include "TempFileManager.h"
|
||||
#include <thread>
|
||||
#include <filesystem>
|
||||
#include <shared_mutex>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#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<std::string, FileControlBlock> 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<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
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<std::string, std::string>& 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<std::chrono::seconds>(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<WebFileInfo> TempFileManager::GetAllFileInfos() const noexcept
|
||||
{
|
||||
std::vector<WebFileInfo> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user