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:
2026-10-14 06:26:13 -05:00
co-authored by Claude Opus 4.8
parent e77b960cc5
commit e9042f091e
6 changed files with 85 additions and 39 deletions
+35
View File
@@ -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();