Fixed a rare crash on app startup caused by ENOTSUP (errno 45) on Mac (#14686)

fix: prevent startup crash when preset-sync directory scan hits a transient FS error

On startup the user-preset sync thread scans the preset folder for orphaned
.info files (scan_orphaned_info_files). It iterated the directory with a
throwing boost::filesystem::directory_iterator while running on a background
thread that has no exception guard. On macOS, readdir() can intermittently
fail with ENOTSUP (errno 45); boost then throws filesystem_error, which --
uncaught on the sync thread -- calls std::terminate and aborts the whole
application on startup.

- Iterate with the error_code-based directory_iterator so a transient read
  failure is logged and skipped instead of thrown. The orphan scan is
  best-effort and re-runs on the next sync, so skipping a cycle is harmless.
  This mirrors the existing pattern in has_json_presets() and the plugin scan.
- Wrap the entire sync-thread body in try/catch as defense-in-depth, so no
  future uncaught exception on that otherwise-unguarded thread can abort the
  app.
This commit is contained in:
SoftFever
2026-07-09 21:57:59 +08:00
committed by GitHub
parent 6fda82476d
commit 05ed2e4dfa

View File

@@ -6965,6 +6965,10 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
// finishFn tears down the progress dialog (and clears the re-entrancy guard), so it
// must run on every exit path — otherwise an early bail-out would leak the modal
// dialog and leave the guard stuck, blocking all later manual syncs.
// Guard the whole thread body: an uncaught exception here (e.g. a transient
// boost::filesystem error while scanning the preset folder) would otherwise
// propagate out of the thread and terminate the entire application.
try {
if (!m_agent) { finishFn(false); return; }
// One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs.
@@ -7206,6 +7210,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by exception: " << e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by unknown exception";
}
});
}
@@ -8526,8 +8535,13 @@ void GUI_App::scan_orphaned_info_files()
if (!fs::exists(type_dir))
continue;
// Iterate through all .info files
for (auto& entry : boost::filesystem::directory_iterator(type_dir)) {
// Iterate through all .info files. Use the error_code-based iterator so a transient
// directory-read failure (e.g. macOS readdir returning ENOTSUP) is logged and skipped
// instead of throwing an uncaught exception that would terminate the app from the
// background sync thread this runs on.
boost::system::error_code ec;
for (boost::filesystem::directory_iterator it(type_dir, ec), end; !ec && it != end; it.increment(ec)) {
const auto& entry = *it;
if (entry.path().extension() != ".info")
continue;
@@ -8546,6 +8560,8 @@ void GUI_App::scan_orphaned_info_files()
}
}
}
if (ec)
BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message();
}
}