perf(market): fetch price and market data on a dedicated HTTP lane
The CoinGecko price, tickers and market chart and the exchange candle GETs ran on the wallet's RPC worker, the same FIFO lane as balance, history and sends. A slow or blocked endpoint held that lane for up to 10-12 s per price or ticker fetch and 2 x 30 s per chart or candle job, and a wallet scan in turn delayed the price. At quit a hung GET held shutdown until the 15 s watchdog cut teardown short. They now run on their own RPCWorker thread; results are applied on the main thread and wake the idle loop. A new util::httpGet takes a cancel flag (checked by libcurl's progress callback), sets CURLOPT_NOSIGNAL and returns the HTTP status and error; httpGetString wraps it. Shutdown sets the flag, so queued GETs return at once and one in flight aborts within about a second. The price uses a plain in-flight flag instead of the connection-scoped refresh job, so a disconnect no longer throws away a good in-flight price and leaves it loading until the next tick. User-visible: - chart and candle GETs use a 5 s connect / 20 s total timeout (were 15 s / 30 s); - the price request goes through the shared GET: User-Agent ObsidianDragon/1.0 (was DragonX-Wallet/1.0) and a 16 MiB reply cap; - market fetches on connect start alongside the wallet refresh instead of queuing behind it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+57
-23
@@ -11,6 +11,7 @@
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -47,34 +48,67 @@ int xferCb(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t, curl
|
||||
return (*fn)(static_cast<double>(dlnow), static_cast<double>(dltotal)) ? 0 : 1;
|
||||
}
|
||||
|
||||
// httpGet's progress callback: abort the transfer (non-zero) once the caller's cancel flag is set.
|
||||
int cancelXferCb(void* clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
|
||||
{
|
||||
return static_cast<const std::atomic<bool>*>(clientp)->load(std::memory_order_relaxed) ? 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HttpGetResult httpGet(const std::string& url, const char* logTag, const HttpGetOptions& opt)
|
||||
{
|
||||
HttpGetResult out;
|
||||
if (opt.cancel && opt.cancel->load(std::memory_order_relaxed)) {
|
||||
out.cancelled = true;
|
||||
out.error = "cancelled";
|
||||
return out;
|
||||
}
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) { out.error = "failed to initialize curl"; return out; }
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out.body);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0");
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, opt.timeoutSec);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, opt.connectTimeoutSec);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); // no SIGALRM for timeouts: these run on worker threads
|
||||
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE, static_cast<curl_off_t>(kMaxMetadataBytes)); // reject oversized metadata
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
if (opt.cancel) {
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, cancelXferCb);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, const_cast<std::atomic<bool>*>(opt.cancel));
|
||||
}
|
||||
const CURLcode res = curl_easy_perform(curl);
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &out.httpStatus);
|
||||
curl_easy_cleanup(curl);
|
||||
out.transportOk = (res == CURLE_OK);
|
||||
out.cancelled = (res == CURLE_ABORTED_BY_CALLBACK);
|
||||
if (!out.transportOk) out.error = out.cancelled ? "cancelled" : curl_easy_strerror(res);
|
||||
if (!out.cancelled && (!out.transportOk || out.httpStatus < 200 || out.httpStatus >= 300)) {
|
||||
DEBUG_LOGF("%s GET %s failed: %s (HTTP %ld)\n",
|
||||
logTag, url.c_str(), curl_easy_strerror(res), out.httpStatus);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string httpGetString(const std::string& url, const char* logTag, const HttpGetOptions& opt)
|
||||
{
|
||||
HttpGetResult r = httpGet(url, logTag, opt);
|
||||
if (!r.transportOk || r.httpStatus < 200 || r.httpStatus >= 300) return {};
|
||||
return std::move(r.body);
|
||||
}
|
||||
|
||||
std::string httpGetString(const std::string& url, const char* logTag,
|
||||
long connectTimeoutSec, long timeoutSec)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) return {};
|
||||
std::string result;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0");
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeoutSec);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, connectTimeoutSec);
|
||||
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE, static_cast<curl_off_t>(kMaxMetadataBytes)); // reject oversized metadata
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
const CURLcode res = curl_easy_perform(curl);
|
||||
long httpCode = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
curl_easy_cleanup(curl);
|
||||
if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) {
|
||||
DEBUG_LOGF("%s GET %s failed: %s (HTTP %ld)\n",
|
||||
logTag, url.c_str(), curl_easy_strerror(res), httpCode);
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
HttpGetOptions opt;
|
||||
opt.connectTimeoutSec = connectTimeoutSec;
|
||||
opt.timeoutSec = timeoutSec;
|
||||
return httpGetString(url, logTag, opt);
|
||||
}
|
||||
|
||||
bool httpDownloadToFile(const std::string& url, const std::string& destPath,
|
||||
|
||||
@@ -11,16 +11,38 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
// GET `url` over TLS (peer + host verified) into a string. Follows redirects; 30s transfer /
|
||||
// 15s connect timeout by default; browser-agnostic User-Agent; responses over 16 MiB are refused.
|
||||
// Returns "" on ANY failure (curl error or non-2xx status). `logTag` (e.g. "[xmrig-updater]")
|
||||
// prefixes the debug log on failure.
|
||||
// Options for httpGet / httpGetString. The defaults are the updaters' (15 s connect, 30 s total).
|
||||
struct HttpGetOptions {
|
||||
long connectTimeoutSec = 15;
|
||||
long timeoutSec = 30;
|
||||
// When non-null and set (before or during the transfer) the GET is abandoned: checked once up front,
|
||||
// then by libcurl's progress callback (about once a second even while resolving / connecting).
|
||||
const std::atomic<bool>* cancel = nullptr;
|
||||
};
|
||||
|
||||
struct HttpGetResult {
|
||||
bool transportOk = false; // curl finished without error (any HTTP status)
|
||||
long httpStatus = 0; // 0 when no response arrived
|
||||
bool cancelled = false; // abandoned through HttpGetOptions::cancel
|
||||
std::string body; // whatever arrived (also for a non-2xx status)
|
||||
std::string error; // curl_easy_strerror text, "cancelled", or "" when transportOk
|
||||
};
|
||||
|
||||
// GET `url` over TLS (peer + host verified). Follows redirects; browser-agnostic User-Agent; responses
|
||||
// over 16 MiB are refused; CURLOPT_NOSIGNAL is set (timeouts are safe on any thread). `logTag` (e.g.
|
||||
// "[xmrig-updater]") prefixes the debug log on failure. The result says what happened.
|
||||
HttpGetResult httpGet(const std::string& url, const char* logTag, const HttpGetOptions& opt = {});
|
||||
|
||||
// The body of a 2xx reply, or "" on ANY failure (curl error, non-2xx status, cancelled).
|
||||
std::string httpGetString(const std::string& url, const char* logTag, const HttpGetOptions& opt);
|
||||
// Same, with the updaters' 15 s connect / 30 s total timeouts by default (unchanged contract).
|
||||
std::string httpGetString(const std::string& url, const char* logTag,
|
||||
long connectTimeoutSec = 15, long timeoutSec = 30);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user