refactor(daemon): share the output ring-window helper
The embedded daemon and the xmrig manager each kept their own copy of the console output ring: append new bytes, trim the front to a cap at a newline boundary, and hand a reader everything since its offset. The two copies had drifted. The daemon tracked an absolute byte total so a reader's offset stayed valid across a front-trim, while the miner keyed its reader off the live buffer size, so a trim could silently rewind the console feed. - The append/trim and the since-offset logic now live in one small header, outputWindowSince and appendBounded, included by both. The trim keeps the newline-boundary cut and the guard that stops a lone trailing newline from erasing the whole buffer. - The xmrig manager now carries the same absolute byte total, so its reader resolves against the window the same way and survives a trim (and a process restart that clears the buffer but keeps the total). Tests cover feeding the ring well past the trim point while a reader polls each chunk (the feed is continuous and never dies at the trim), the lone-trailing-newline guard, and a cleared buffer with a retained total. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -276,18 +276,7 @@ static constexpr size_t MAX_OUTPUT_BYTES = 1024 * 1024; // 1 MB
|
||||
void EmbeddedDaemon::appendOutput(const char* data, size_t len)
|
||||
{
|
||||
// Caller must hold output_mutex_
|
||||
process_output_.append(data, len);
|
||||
output_total_ += len;
|
||||
if (process_output_.size() > MAX_OUTPUT_BYTES + MAX_OUTPUT_BYTES / 4) {
|
||||
// Trim to MAX_OUTPUT_BYTES, cutting at a newline boundary
|
||||
size_t trim_to = process_output_.size() - MAX_OUTPUT_BYTES;
|
||||
size_t nl = process_output_.find('\n', trim_to);
|
||||
if (nl != std::string::npos && nl < process_output_.size() - 1) {
|
||||
process_output_.erase(0, nl + 1);
|
||||
} else {
|
||||
process_output_.erase(0, trim_to);
|
||||
}
|
||||
}
|
||||
appendBounded(process_output_, output_total_, data, len, MAX_OUTPUT_BYTES);
|
||||
}
|
||||
|
||||
std::vector<std::string> EmbeddedDaemon::getRecentLines(int maxLines) const
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "output_window.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
@@ -171,15 +173,7 @@ public:
|
||||
/** getOutputSince() on a buffer holding the LAST buffer.size() of `total` bytes ever appended. */
|
||||
static std::string outputSince(const std::string& buffer, size_t total, size_t& offset)
|
||||
{
|
||||
const size_t base = total - buffer.size(); // absolute position of buffer[0]
|
||||
if (offset < base) offset = base; // trimmed before it was read: resume at what's left
|
||||
if (offset >= total) {
|
||||
offset = total;
|
||||
return {};
|
||||
}
|
||||
std::string result = buffer.substr(offset - base);
|
||||
offset = total;
|
||||
return result;
|
||||
return outputWindowSince(buffer, total, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Shared absolute-offset output ring helpers (daemon debug.log feed, xmrig console feed).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
namespace daemon {
|
||||
|
||||
// getOutputSince() over a buffer holding the LAST buffer.size() of `total` bytes ever appended.
|
||||
inline std::string outputWindowSince(const std::string& buffer, std::size_t total, std::size_t& offset)
|
||||
{
|
||||
const std::size_t base = total - buffer.size(); // absolute position of buffer[0]
|
||||
if (offset < base) offset = base; // trimmed before read: resume at what's left
|
||||
if (offset >= total) {
|
||||
offset = total;
|
||||
return {};
|
||||
}
|
||||
std::string result = buffer.substr(offset - base);
|
||||
offset = total;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Append `n` bytes, advance absolute `total`, and trim to ~cap at a newline once size exceeds 1.25x cap.
|
||||
// The `nl < size-1` guard keeps a lone trailing newline from erasing the whole buffer.
|
||||
inline void appendBounded(std::string& buffer, std::size_t& total, const char* data, std::size_t n, std::size_t cap)
|
||||
{
|
||||
buffer.append(data, n);
|
||||
total += n;
|
||||
if (buffer.size() > cap + cap / 4) {
|
||||
const std::size_t trim_to = buffer.size() - cap;
|
||||
const std::size_t nl = buffer.find('\n', trim_to);
|
||||
if (nl != std::string::npos && nl < buffer.size() - 1) buffer.erase(0, nl + 1);
|
||||
else buffer.erase(0, trim_to);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
@@ -613,17 +613,7 @@ void XmrigManager::drainOutput() {
|
||||
|
||||
void XmrigManager::appendOutput(const char* data, size_t len) {
|
||||
// Caller must hold output_mutex_
|
||||
static constexpr size_t MAX_OUTPUT = 1024 * 1024; // 1 MB cap
|
||||
process_output_.append(data, len);
|
||||
if (process_output_.size() > MAX_OUTPUT) {
|
||||
// Trim from the front at a newline boundary
|
||||
size_t cut = process_output_.size() - MAX_OUTPUT;
|
||||
auto pos = process_output_.find('\n', cut);
|
||||
if (pos != std::string::npos)
|
||||
process_output_.erase(0, pos + 1);
|
||||
else
|
||||
process_output_.erase(0, cut);
|
||||
}
|
||||
appendBounded(process_output_, output_total_, data, len, 1024 * 1024); // 1 MB cap (1.25x hysteresis)
|
||||
}
|
||||
|
||||
std::vector<std::string> XmrigManager::getRecentLines(int maxLines) const {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include "output_window.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
@@ -106,13 +108,7 @@ public:
|
||||
*/
|
||||
std::string getOutputSince(size_t& offset) const {
|
||||
std::lock_guard<std::mutex> lk(output_mutex_);
|
||||
if (offset >= process_output_.size()) {
|
||||
offset = process_output_.size();
|
||||
return {};
|
||||
}
|
||||
std::string result = process_output_.substr(offset);
|
||||
offset = process_output_.size();
|
||||
return result;
|
||||
return outputWindowSince(process_output_, output_total_, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +165,7 @@ private:
|
||||
|
||||
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;
|
||||
|
||||
@@ -6071,6 +6071,40 @@ void testOutputSinceAcrossTrim()
|
||||
EXPECT_TRUE(got.empty());
|
||||
}
|
||||
|
||||
void testOutputWindowSaturation()
|
||||
{
|
||||
using dragonx::daemon::appendBounded;
|
||||
using dragonx::daemon::outputWindowSince;
|
||||
const size_t cap = 1024 * 1024;
|
||||
std::string buf, seen, expected;
|
||||
size_t total = 0, offset = 0;
|
||||
int n = 0;
|
||||
// Feed well past the 1.25x trim point in 4 KB chunks of whole lines; the reader polls every chunk.
|
||||
while (total < cap * 2) {
|
||||
std::string chunk;
|
||||
while (chunk.size() < 4096) chunk += "line " + std::to_string(n++) + "\n";
|
||||
appendBounded(buf, total, chunk.data(), chunk.size(), cap);
|
||||
expected += chunk;
|
||||
seen += outputWindowSince(buf, total, offset);
|
||||
}
|
||||
EXPECT_TRUE(buf.size() <= cap + cap / 4);
|
||||
EXPECT_TRUE(buf.size() > cap / 2); // trimmed, but not wiped
|
||||
EXPECT_EQ(total, expected.size());
|
||||
EXPECT_EQ(seen, expected); // the feed never dies at the trim
|
||||
EXPECT_EQ(offset, total);
|
||||
// A lone trailing newline must not erase the whole buffer.
|
||||
std::string b2(cap + cap / 4 - 1, 'x');
|
||||
size_t t2 = b2.size();
|
||||
appendBounded(b2, t2, "\n", 1, cap);
|
||||
EXPECT_TRUE(b2.size() >= cap);
|
||||
// clear()-then-keep-total (process restart) keeps absolute offsets valid.
|
||||
buf.clear();
|
||||
const std::string fresh = "new run\n";
|
||||
appendBounded(buf, total, fresh.data(), fresh.size(), cap);
|
||||
EXPECT_EQ(outputWindowSince(buf, total, offset), fresh);
|
||||
EXPECT_EQ(offset, total);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------
|
||||
// S6: the busy gate (don't flood a busy node's RPC queue) and the connect backoff.
|
||||
// ---------------------------------------------------------------------------------------------------
|
||||
@@ -18621,6 +18655,7 @@ int main()
|
||||
testDaemonBusyTracker();
|
||||
testShouldConfirmStop();
|
||||
testOutputSinceAcrossTrim();
|
||||
testOutputWindowSaturation();
|
||||
testChooseDaemonStatus();
|
||||
testDaemonStopStringsTranslated();
|
||||
testMiningAddressStringsTranslated();
|
||||
|
||||
Reference in New Issue
Block a user