mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-25 09:50:59 +00:00
Keep a Save From Being Lost to an Open Reader and Cap the Lock Wait
Windows refuses to replace a file that another process holds open without FILE_SHARE_DELETE, which is how the C runtime opens files for reading, so the atomic write could fail against a concurrent reader and drop the save where the old in-place write had succeeded. Lock the readers that were still outside the guard, Preset::reload() and the physical printer loader and writer, and when the rename still fails that way, log it and write in place as before; losing the save is worse than a torn read. Report the OS error from a failed write instead of a generic I/O error. Each leaf guard paid the full two-second wait on its own, so a bulk save against an instance holding the lock for a long scan stalled once per preset while holding the preset collection mutex. After a timed-out wait the same lock file is not waited on again for ten seconds. Read-only scans never rewrite or delete and many CLI jobs may share one data dir, so they take no lock and no longer queue behind each other or log about writing. Take the bundle metadata guard beside the write rather than while the JSON is built, state the lock-order rule in the header, and give the tests a temporary file rather than a directory, since the process keeps the lock file open and a directory holding it cannot be removed on Windows.
This commit is contained in:
+24
-8
@@ -711,21 +711,37 @@ std::error_code rename_file(const std::string &from, const std::string &to)
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::error_code write_whole_file(const std::string &path, const std::string &content)
|
||||
{
|
||||
errno = 0;
|
||||
boost::nowide::ofstream out(path, std::ios::out | std::ios::trunc);
|
||||
out << content;
|
||||
out.close();
|
||||
if (! out.fail())
|
||||
return {};
|
||||
return std::make_error_code(errno != 0 ? static_cast<std::errc>(errno) : std::errc::io_error);
|
||||
}
|
||||
|
||||
std::error_code write_file_atomically(const std::string &path, const std::string &content)
|
||||
{
|
||||
const std::string tmp_path = path + "." + std::to_string(get_current_pid()) + ".tmp";
|
||||
{
|
||||
boost::nowide::ofstream out(tmp_path, std::ios::out | std::ios::trunc);
|
||||
out << content;
|
||||
out.close();
|
||||
if (out.fail()) {
|
||||
boost::nowide::remove(tmp_path.c_str());
|
||||
return std::make_error_code(std::errc::io_error);
|
||||
}
|
||||
if (std::error_code ec = write_whole_file(tmp_path, content)) {
|
||||
boost::nowide::remove(tmp_path.c_str());
|
||||
return ec;
|
||||
}
|
||||
std::error_code ec = rename_file(tmp_path, path);
|
||||
if (ec)
|
||||
boost::nowide::remove(tmp_path.c_str());
|
||||
#ifdef _WIN32
|
||||
// Windows refuses to replace a file another process holds open without
|
||||
// FILE_SHARE_DELETE, which is how the C runtime opens files for reading.
|
||||
// Losing the save is worse than a reader seeing a partial file, so write
|
||||
// in place the way this used to work before the atomic path existed.
|
||||
if (ec == std::errc::permission_denied) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Cannot replace " << path << " while another process holds it open; writing in place";
|
||||
ec = write_whole_file(path, content);
|
||||
}
|
||||
#endif
|
||||
return ec;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user