26constexpr const char charset[] =
"0123456789"
27 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
28 "abcdefghijklmnopqrstuvwxyz";
30constexpr size_t max_index = (
sizeof(charset) - 1);
32using random_generator_t = ::std::mt19937;
34static const auto make_seeded_engine = []()
37 std::hash<std::thread::id> hasher;
38 std::seed_seq seed(std::initializer_list<typename random_generator_t::result_type>{
39 static_cast<typename random_generator_t::result_type
>(
40 std::chrono::system_clock::now().time_since_epoch().count()),
41 static_cast<typename random_generator_t::result_type
>(hasher(std::this_thread::get_id())),
50 return random_generator_t(seed);
53std::string generate_random_string(
size_t len)
55 static thread_local random_generator_t
engine(make_seeded_engine());
57 std::uniform_int_distribution<>
dist(0, max_index);
62 for (
size_t i = 0; i < len; i++)
74auto looks_like_temp_file(
const fs::path& p)
noexcept ->
bool
76 const auto name = p.filename().string();
77 if(
name.size() < 2 ||
name.front() !=
'.')
81 return p.extension() ==
".temp";
85auto remove_temp_with_retry(
const fs::path& temp,
88 std::string* last_error_out)
noexcept -> bool;
96void try_remove_stale_temp(
const fs::path& p,
97 fs::file_time_type now,
98 std::chrono::seconds min_age,
99 std::size_t& removed_counter)
noexcept
101 if(!looks_like_temp_file(p))
106 fs::error_code stat_ec;
107 const auto write_time = fs::last_write_time(p, stat_ec);
112 if(std::chrono::duration_cast<std::chrono::seconds>(now - write_time) < min_age)
118 std::string diagnostic;
119 if(remove_temp_with_retry(p, 5, 10, &diagnostic))
122 APPLOG_INFO(
"asset_writer: Removed stale temp file: {}", p.generic_string());
126 APPLOG_WARNING(
"asset_writer: Could not remove stale temp file: {} ({})",
134auto sync_file(
const fs::path& temp, fs::error_code& ec)
noexcept ->
bool
139 HANDLE h = CreateFileW(temp.wstring().c_str(),
141 FILE_SHARE_READ | FILE_SHARE_WRITE,
144 FILE_ATTRIBUTE_NORMAL,
146 if(h == INVALID_HANDLE_VALUE)
148 ec = fs::error_code(
static_cast<int>(GetLastError()), std::system_category());
151 if(!FlushFileBuffers(h))
153 ec = fs::error_code(
static_cast<int>(GetLastError()), std::system_category());
160 int fd = ::open(temp.c_str(), O_RDWR);
163 ec = fs::error_code(errno, std::generic_category());
168 ec = fs::error_code(errno, std::generic_category());
180auto atomic_rename_file(
const fs::path& src,
const fs::path& dst, fs::error_code& ec)
noexcept ->
bool
183 fs::rename(src, dst, ec);
196auto atomic_rename_with_retry(
const fs::path& src,
200 int base_delay_ms = 10) noexcept ->
bool
202 for(
int i = 0; i < max_retries; ++i)
205 fs::rename(src, dst, ec);
210 std::this_thread::sleep_for(std::chrono::milliseconds(base_delay_ms * (1 << i)));
230auto windows_force_delete(
const fs::path& path, DWORD& last_error)
noexcept ->
bool
234 HANDLE
h = CreateFileW(path.wstring().c_str(),
235 DELETE | SYNCHRONIZE,
236 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
239 FILE_ATTRIBUTE_NORMAL,
241 if(h == INVALID_HANDLE_VALUE)
243 last_error = GetLastError();
249#if defined(FILE_DISPOSITION_FLAG_POSIX_SEMANTICS)
250 FILE_DISPOSITION_INFO_EX disp_ex{};
251 disp_ex.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;
252 ok = (SetFileInformationByHandle(h, FileDispositionInfoEx, &disp_ex,
sizeof(disp_ex)) != FALSE);
255 last_error = GetLastError();
261 FILE_DISPOSITION_INFO disp{};
262 disp.DeleteFile = TRUE;
263 ok = (SetFileInformationByHandle(h, FileDispositionInfo, &disp,
sizeof(disp)) != FALSE);
266 last_error = GetLastError();
287auto remove_temp_with_retry(
const fs::path& temp,
289 int base_delay_ms = 10,
290 std::string* last_error_out =
nullptr) noexcept ->
bool
292 fs::error_code remove_ec;
293 for(
int i = 0;
i < max_retries; ++
i)
295 fs::remove(temp, remove_ec);
296 if(!remove_ec || !fs::exists(temp, remove_ec))
302 const int shift = std::min(i, 16);
303 const int sleep_ms = std::min(base_delay_ms * (1 << shift), 500);
304 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
313 if(windows_force_delete(temp, win_err))
317 if(last_error_out !=
nullptr)
319 *last_error_out =
"fs::remove=" + remove_ec.message() +
320 " (code " + std::to_string(remove_ec.value()) +
"); " +
321 "windows_force_delete=" + std::to_string(win_err);
325 if(last_error_out !=
nullptr)
328 "fs::remove=" + remove_ec.message() +
" (code " + std::to_string(remove_ec.value()) +
")";
344auto deferred_cleanup_mutex() -> std::mutex&
350auto deferred_cleanup_paths() -> std::vector<fs::path>&
352 static std::vector<fs::path>
v;
356void enqueue_deferred_cleanup(fs::path p)
noexcept
360 std::lock_guard<std::mutex> lk(deferred_cleanup_mutex());
361 deferred_cleanup_paths().push_back(std::move(p));
370void drain_deferred_cleanup() noexcept
372 std::vector<fs::path> snapshot;
375 std::lock_guard<std::mutex> lk(deferred_cleanup_mutex());
376 snapshot.swap(deferred_cleanup_paths());
388 std::vector<fs::path> still_pending;
389 still_pending.reserve(snapshot.size());
391 for(
auto& p : snapshot)
396 fs::error_code probe_ec;
397 if(!fs::exists(p, probe_ec))
403 if(remove_temp_with_retry(p, 3, 5))
405 APPLOG_TRACE(
"asset_writer: Deferred cleanup removed temp file: {}",
p.generic_string());
409 still_pending.push_back(std::move(p));
413 if(!still_pending.empty())
417 std::lock_guard<std::mutex> lk(deferred_cleanup_mutex());
418 auto& dst = deferred_cleanup_paths();
419 dst.insert(dst.end(),
420 std::make_move_iterator(still_pending.begin()),
421 std::make_move_iterator(still_pending.end()));
442 explicit temp_file_guard(fs::path path) noexcept : path_(std::move(path))
446 ~temp_file_guard() noexcept
448 if(committed_ || path_.empty())
452 fs::error_code probe_ec;
453 if(!fs::exists(path_, probe_ec))
459 std::string diagnostic;
460 if(remove_temp_with_retry(path_, 8, 10, &diagnostic))
469 "asset_writer: Temp file still locked after immediate retries; queued for deferred cleanup: {} ({})",
470 path_.generic_string(),
472 enqueue_deferred_cleanup(path_);
475 temp_file_guard(
const temp_file_guard&) =
delete;
476 auto operator=(
const temp_file_guard&) -> temp_file_guard& =
delete;
477 temp_file_guard(temp_file_guard&&) =
delete;
478 auto operator=(temp_file_guard&&) -> temp_file_guard& =
delete;
480 void commit() noexcept
487 bool committed_{
false};
497auto make_temp_path(
const fs::path& dir, fs::path& out, fs::error_code& ec)
noexcept ->
bool
500 if(!fs::exists(dir, ec) || ec)
504 if(!fs::is_directory(dir, ec) || ec)
509 constexpr int max_attempts = 100;
510 for(
int attempt = 0; attempt < max_attempts; ++attempt)
512 out = dir / (
"." + hpp::to_string(
generate_uuid()) +
".temp");
513 fs::error_code exists_ec;
514 const bool exists = fs::exists(out, exists_ec);
523 out.make_preferred();
528 ec = std::make_error_code(std::errc::file_exists);
541auto atomic_copy_file(
const fs::path& src,
const fs::path& dst, fs::error_code& ec)
noexcept ->
bool
548 drain_deferred_cleanup();
550 if(!fs::exists(src, ec) || ec)
554 ec = std::make_error_code(std::errc::no_such_file_or_directory);
558 if(!fs::is_regular_file(src, ec) || ec)
562 ec = std::make_error_code(std::errc::invalid_argument);
573 temp_file_guard guard(temp);
575 fs::copy_file(src, temp, fs::copy_options::overwrite_existing, ec);
586 if(!atomic_rename_with_retry(temp, dst, ec))
596 const std::function<
void(
const fs::path&)>& callback,
597 fs::error_code& ec)
noexcept
602 drain_deferred_cleanup();
610 temp_file_guard guard(temp);
614 if(!fs::exists(temp, ec) || ec)
618 ec = std::make_error_code(std::errc::no_such_file_or_directory);
628 if(!atomic_rename_with_retry(temp, dst, ec))
643 std::chrono::seconds min_age)
noexcept -> std::size_t
648 drain_deferred_cleanup();
651 if(!fs::exists(dir, ec) || ec)
655 if(!fs::is_directory(dir, ec) || ec)
660 const auto now = fs::file_time_type::clock::now();
661 std::size_t removed = 0;
663 auto walk = [&](
const auto& begin,
const auto& end) ->
void
665 for(
auto it = begin; it != end; ++it)
667 const auto&
entry = *it;
668 fs::error_code is_file_ec;
669 if(
entry.is_regular_file(is_file_ec) && !is_file_ec)
671 try_remove_stale_temp(
entry.path(), now, min_age, removed);
676 fs::error_code iter_ec;
679 walk(fs::recursive_directory_iterator(dir, iter_ec), fs::recursive_directory_iterator{});
683 walk(fs::directory_iterator(dir, iter_ec), fs::directory_iterator{});