test(tools): match dragonxd's lock-free getrescaninfo; count requests per method
- getrescaninfo takes no lock in dragonxd (it only reads wallet fields), so the stub no longer holds it behind cs_main. Holding it there made the GUI's rescan-progress poll look like an abandoned request every 30 s during a simulated rebuild. - a `methods` event lists the 10 most-requested JSON-RPC methods with counts (alongside `stats`, and at exit): what the GUI is polling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ What it copies from the real daemon (DragonX v1.3.0):
|
||||
command line or `DRAGONX.conf`). When the queue is full the request gets an immediate plain-text
|
||||
`HTTP 500 "Work queue depth exceeded"`. A request whose client timed out **keeps its slot** until
|
||||
a worker reaches it.
|
||||
- **cs_main.** One global lock. Every method except `stop` and `help` takes it. A "witness rebuild"
|
||||
- **cs_main.** One global lock. Every method except `stop`, `help` and `getrescaninfo` (lock-free in dragonxd too) takes it. A "witness rebuild"
|
||||
holds it for `FAKE_REBUILD_SECS` and logs the real markers:
|
||||
- `Reading blocks for witness rebuild: <off> / <total>` (Phase 1, abortable);
|
||||
- nothing during Phase 2, which ignores shutdown;
|
||||
@@ -53,6 +53,7 @@ Everything that matters for a test is appended to `<datadir>/fake_dragonxd.event
|
||||
| `sigterm` | SIGTERM/SIGINT received (POSIX) |
|
||||
| `http-interrupted`, `shutdown-begin` | shutdown progress |
|
||||
| `stats ...` | counters, every 30 s when they change |
|
||||
| `methods ...` | the 10 most-requested JSON-RPC methods with counts (with `stats`, and at exit): what the GUI is polling |
|
||||
| `clean-exit ...` | **graceful exit**, with final counters. If this is missing, the process was killed. |
|
||||
|
||||
## Build
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// exceeded" before any handler runs, and a request whose client gave up keeps its slot until a
|
||||
// worker reaches it (libevent has no close callback);
|
||||
// * a global cs_main that a "witness rebuild" holds for minutes, logging the real markers. Every
|
||||
// method except the lock-free `stop` / `help` takes it;
|
||||
// method except the lock-free `stop` / `help` / `getrescaninfo` takes it;
|
||||
// * dragonxd's shutdown: `stop` (or SIGTERM/SIGINT on POSIX; Windows has no signal channel, as in
|
||||
// v1.3.0) unbinds the listener at once, answers new requests on open connections with 503, then
|
||||
// waits for the rebuild (Phase 2 is not abortable) before "Shutdown: In progress..." -> flush ->
|
||||
@@ -330,6 +330,23 @@ struct Stats {
|
||||
|
||||
Stats g_stats;
|
||||
|
||||
// Requests per JSON-RPC method, for "what is the GUI polling" (the `methods` event).
|
||||
std::mutex g_methodsMx;
|
||||
std::map<std::string, long> g_methodCounts;
|
||||
|
||||
std::string topMethodsLine(size_t n)
|
||||
{
|
||||
std::vector<std::pair<long, std::string>> v;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_methodsMx);
|
||||
for (const auto& kv : g_methodCounts) v.push_back({kv.second, kv.first});
|
||||
}
|
||||
std::sort(v.rbegin(), v.rend());
|
||||
std::string out;
|
||||
for (size_t i = 0; i < v.size() && i < n; ++i) out += (i ? " " : "") + v[i].second + "=" + std::to_string(v[i].first);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool raiseMax(std::atomic<int>& max, int value)
|
||||
{
|
||||
int cur = max.load();
|
||||
@@ -541,11 +558,16 @@ json execOne(const json& req, int& status)
|
||||
if (!req.is_object() || !req.contains("method") || !req["method"].is_string())
|
||||
return fail(-32600, "Missing method");
|
||||
const std::string m = req["method"].get<std::string>();
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_methodsMx);
|
||||
++g_methodCounts[m];
|
||||
}
|
||||
json params = req.contains("params") ? req["params"] : json::array();
|
||||
if (!params.is_array()) params = json::array();
|
||||
status = 200;
|
||||
|
||||
const bool lockFree = (m == "stop" || m == "help");
|
||||
// Like dragonxd: stop and help take no lock, nor does getrescaninfo (it only reads wallet fields).
|
||||
const bool lockFree = (m == "stop" || m == "help" || m == "getrescaninfo");
|
||||
if (!lockFree && !cannedReply(m, params).known) return fail(-32601, "Method not found");
|
||||
if (g_warmup.load() && !allowedInWarmup(m)) return fail(-28, warmupStatus());
|
||||
|
||||
@@ -561,7 +583,9 @@ json execOne(const json& req, int& status)
|
||||
}
|
||||
|
||||
Canned c;
|
||||
{
|
||||
if (lockFree) {
|
||||
c = cannedReply(m, params);
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lk(g_csMain);
|
||||
c = cannedReply(m, params);
|
||||
if (m == "setgenerate") g_minerOn = !params.empty() && params[0].is_boolean() && params[0].get<bool>();
|
||||
@@ -1264,7 +1288,10 @@ int main(int argc, char** argv)
|
||||
if (Clock::now() - lastStats >= 30s) {
|
||||
lastStats = Clock::now();
|
||||
const std::string s = statsLine();
|
||||
if (s != lastStatsLine) event("stats", s);
|
||||
if (s != lastStatsLine) {
|
||||
event("stats", s);
|
||||
event("methods", topMethodsLine(10));
|
||||
}
|
||||
lastStatsLine = s;
|
||||
}
|
||||
}
|
||||
@@ -1304,6 +1331,7 @@ int main(int argc, char** argv)
|
||||
fs::remove(cookieFile, ec2);
|
||||
}
|
||||
std::this_thread::sleep_for(200ms); // let connection threads flush their last replies
|
||||
event("methods", topMethodsLine(10));
|
||||
event("clean-exit", statsLine());
|
||||
exitNow(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user