fix(util): use the POSIX daemon binary name, scan versions off-thread

The installed-daemon lookup built the binary path from the Windows
resource key "dragonxd.exe", which never exists on Linux or macOS, so
the installed full node was never found there. The version scan also
read the whole binary, now around 200 MB, into memory on the UI thread.

- A new DAEMON_BINARY_NAME gives the daemon's file name on this platform
  ("dragonxd" on POSIX, "dragonxd.exe" on Windows); the old resource
  constant stays only as the embedded-table key. The installed daemon is
  now found on POSIX.
- The version-stamp scan streams the file in 4 MiB chunks with bounded
  memory, carrying bytes across each boundary so a stamp straddling it is
  neither cut short nor missed, and stops at the first match. The result
  is cached by path, size and mtime, so only a changed binary is
  rescanned.
- Settings stats the binary on the UI thread but runs the streamed scan
  on a worker, guarded so a new scan is not started while one is still
  running (the future blocks in its destructor), and the version fills
  in when it lands.

Tests: the POSIX path and binary name, a version stamp in a small file,
a stamp straddling the 4 MiB chunk boundary, and a file with no stamp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 21:42:23 -05:00
co-authored by Claude Opus 4.8
parent 21dc874988
commit abc82136c4
4 changed files with 117 additions and 17 deletions
+21 -2
View File
@@ -51,9 +51,11 @@
#include <set>
#include <filesystem>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <future>
namespace dragonx {
namespace ui {
@@ -186,6 +188,7 @@ struct SettingsPageState {
// since reading the installed binary to scan its version is a one-off disk read).
bool daemon_info_loaded = false;
dragonx::resources::DaemonBinaryInfo installed_daemon;
std::future<dragonx::resources::DaemonBinaryInfo> daemon_version_scan; // streamed version scan, off the UI thread (C123)
dragonx::resources::BundledDaemonInfo bundled_daemon;
bool confirm_restart_daemon = false;
bool confirm_lite_redownload = false;
@@ -2418,10 +2421,26 @@ void RenderSettingsPage(App* app) {
// -------------------- DAEMON BINARY --------------------
if (app->supportsFullNodeLifecycleActions()) {
if (!s_settingsState.daemon_info_loaded) {
s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo();
// A std::async future BLOCKS in its destructor until the task finishes. The Refresh/
// post-install buttons set daemon_info_loaded=false; don't start (and so reassign) a new
// scan while the previous one is still running — defer the reload until it lands (C123).
const bool scanBusy = s_settingsState.daemon_version_scan.valid() &&
s_settingsState.daemon_version_scan.wait_for(std::chrono::seconds(0)) != std::future_status::ready;
if (!s_settingsState.daemon_info_loaded && !scanBusy) {
// Stat on the UI thread; the version stamp is a streamed read of a ~200 MB binary on
// POSIX, so it runs on a worker and fills in when done (C123).
s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo(/*scanVersion=*/false);
s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo();
s_settingsState.daemon_info_loaded = true;
if (s_settingsState.installed_daemon.exists)
s_settingsState.daemon_version_scan = std::async(std::launch::async,
[] { return dragonx::resources::getInstalledDaemonInfo(/*scanVersion=*/true); });
}
if (s_settingsState.daemon_version_scan.valid()) {
if (s_settingsState.daemon_version_scan.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
s_settingsState.installed_daemon = s_settingsState.daemon_version_scan.get();
else
ui::material::RequestProgressFrame(); // keep polling until the scan lands
}
const auto& inst = s_settingsState.installed_daemon;
const auto& bun = s_settingsState.bundled_daemon;