The miner's stats and pool-API fetches are blocking HTTP calls on the monitor thread with multi-second timeouts. When the miner was stopped, stop() still had to wait out an in-flight request before the monitor thread could join. - Both fetches now install a curl progress callback that aborts the transfer as soon as a stop is requested, so a request in flight ends promptly instead of running down its timeout. - An abort triggered by stop is treated as a clean cancellation, not a fetch failure, so it is not logged or counted against the failure warning. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
197 lines
6.8 KiB
C++
197 lines
6.8 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
|
|
#include "output_window.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
namespace dragonx {
|
|
namespace daemon {
|
|
|
|
/**
|
|
* @brief Manages the xmrig pool mining process
|
|
*
|
|
* Handles starting/stopping xmrig for pool mining, polling stats via its
|
|
* HTTP API, and capturing stdout for the log panel. Modelled on
|
|
* EmbeddedDaemon (same fork/pipe/monitor pattern, same #ifdef split).
|
|
*/
|
|
class XmrigManager {
|
|
public:
|
|
enum class State { Stopped, Starting, Running, Stopping, Error };
|
|
|
|
/// Live stats polled from xmrig HTTP API (/2/summary)
|
|
struct PoolStats {
|
|
double hashrate_10s = 0;
|
|
double hashrate_60s = 0;
|
|
double hashrate_15m = 0;
|
|
int64_t accepted = 0;
|
|
int64_t rejected = 0;
|
|
int64_t uptime_sec = 0;
|
|
double pool_diff = 0;
|
|
std::string pool_url;
|
|
std::string algo;
|
|
std::string version; // miner version reported by the running xmrig API
|
|
bool connected = false;
|
|
// Memory usage
|
|
int64_t memory_free = 0; // bytes
|
|
int64_t memory_total = 0; // bytes
|
|
int64_t memory_used = 0; // bytes (resident set size)
|
|
int threads_active = 0; // actual mining threads
|
|
// Pool-side hashrate (from pool stats API, not xmrig)
|
|
double pool_hashrate = 0;
|
|
};
|
|
|
|
/// User-facing config (maps 1:1 to UI fields / Settings)
|
|
struct Config {
|
|
std::string pool_url = "pool.dragonx.is:3433";
|
|
std::string wallet_address;
|
|
std::string worker_name = "x";
|
|
std::string algo = "rx/hush";
|
|
int threads = 0; // 0 = xmrig auto
|
|
bool tls = false;
|
|
bool hugepages = true;
|
|
};
|
|
|
|
XmrigManager();
|
|
~XmrigManager();
|
|
|
|
// Non-copyable
|
|
XmrigManager(const XmrigManager&) = delete;
|
|
XmrigManager& operator=(const XmrigManager&) = delete;
|
|
|
|
/**
|
|
* @brief Start xmrig with the given config.
|
|
* Generates a JSON config file, finds the binary, and spawns the process.
|
|
*/
|
|
bool start(const Config& cfg);
|
|
|
|
/**
|
|
* @brief Stop xmrig gracefully (SIGTERM → wait → SIGKILL).
|
|
*/
|
|
void stop(int wait_ms = 5000);
|
|
|
|
bool isRunning() const;
|
|
State getState() const { return state_.load(std::memory_order_relaxed); }
|
|
|
|
// Return COPIES under lock: stats_ and last_error_ are mutated by the monitor thread while the UI
|
|
// thread reads them, so handing out a reference is a torn-read / use-after-free race (M-03, M-04).
|
|
PoolStats getStats() const { std::lock_guard<std::mutex> lk(stats_mutex_); return stats_; }
|
|
std::string getLastError() const { std::lock_guard<std::mutex> lk(error_mutex_); return last_error_; }
|
|
|
|
/// Thread count requested at start() — available immediately, unlike
|
|
/// PoolStats::threads_active which requires an API response.
|
|
int getRequestedThreads() const { return threads_; }
|
|
|
|
/**
|
|
* @brief Get last N lines of xmrig stdout (thread-safe snapshot).
|
|
*/
|
|
std::vector<std::string> getRecentLines(int maxLines = 30) const;
|
|
|
|
/**
|
|
* @brief Get new output since a given offset (thread-safe).
|
|
* Returns the new text and updates offset to the current size.
|
|
*/
|
|
std::string getOutputSince(size_t& offset) const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
return outputWindowSince(process_output_, output_total_, offset);
|
|
}
|
|
|
|
/**
|
|
* @brief Get current output size (thread-safe, no copy)
|
|
*/
|
|
size_t getOutputSize() const {
|
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
|
return process_output_.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Poll the xmrig HTTP API for live stats.
|
|
* Lightweight — reads cached stats updated by the monitor thread.
|
|
* Called from App::update() every ~2 s while running.
|
|
*/
|
|
void pollStats();
|
|
|
|
/**
|
|
* @brief Get xmrig process memory usage in MB (from OS, not API).
|
|
*/
|
|
double getMemoryUsageMB() const;
|
|
|
|
/**
|
|
* @brief Find xmrig binary in standard locations.
|
|
*/
|
|
static std::string findXmrigBinary();
|
|
|
|
/**
|
|
* @brief Kick a one-shot background `<binary> --version` detection (idempotent).
|
|
* Lets the UI show the installed miner's version before mining is ever started.
|
|
*/
|
|
static void startVersionDetection();
|
|
|
|
/**
|
|
* @brief Cached installed-miner version parsed by startVersionDetection().
|
|
* Empty until detection completes (or if no binary/parse failed). Thread-safe.
|
|
*/
|
|
static std::string installedVersion();
|
|
|
|
private:
|
|
bool generateConfig(const Config& cfg, const std::string& outPath);
|
|
bool startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads);
|
|
void monitorProcess();
|
|
void drainOutput();
|
|
void appendOutput(const char* data, size_t len);
|
|
// Set last_error_ under error_mutex_ (writers run on both the main thread and the monitor thread).
|
|
void setLastError(std::string e) { std::lock_guard<std::mutex> lk(error_mutex_); last_error_ = std::move(e); }
|
|
void fetchStatsHttp(); // Blocking HTTP call — runs on monitor thread only
|
|
// curl xfer-info callback: abort an in-flight stats fetch once stop() is requested.
|
|
static int abortCb(void* p, int64_t, int64_t, int64_t, int64_t)
|
|
{ return static_cast<const XmrigManager*>(p)->should_stop_.load() ? 1 : 0; }
|
|
void fetchPoolApiStats(); // Fetch pool-side stats (hashrate) from pool HTTP API
|
|
|
|
std::atomic<State> state_{State::Stopped};
|
|
std::string last_error_;
|
|
mutable std::mutex error_mutex_; // guards last_error_ (written by main + monitor threads)
|
|
|
|
mutable std::mutex output_mutex_;
|
|
std::string process_output_;
|
|
std::size_t output_total_ = 0; // bytes ever appended (absolute end; guarded by output_mutex_, kept across restarts)
|
|
|
|
// xmrig HTTP API credentials (random per session)
|
|
int api_port_ = 0;
|
|
std::string api_token_;
|
|
std::atomic<int> threads_{0}; // Thread count for mining (written on the mining-control thread, read by the UI)
|
|
std::string pool_host_; // Pool hostname for stats API
|
|
PoolStats stats_;
|
|
mutable std::mutex stats_mutex_;
|
|
|
|
// Process handles
|
|
#ifdef _WIN32
|
|
std::atomic<HANDLE> process_handle_{nullptr};
|
|
std::atomic<HANDLE> stdout_read_{nullptr};
|
|
#else
|
|
std::atomic<pid_t> process_pid_{0};
|
|
std::atomic<int> stdout_fd_{-1};
|
|
#endif
|
|
std::mutex io_mutex_; // serializes drainOutput(); lock order io_mutex_ -> output_mutex_
|
|
|
|
std::thread monitor_thread_;
|
|
std::atomic<bool> should_stop_{false};
|
|
};
|
|
|
|
} // namespace daemon
|
|
} // namespace dragonx
|