core: fix Progress.speed_bps reading 0 on the polled path

DAEMON reported Progress.speed_bps reading 0 for the whole life of a live
throttled download while downloaded bytes visibly advanced. DAEMON reads
progress by polling DownloadHandle::progress() (engine_port_core.hpp), not
the on_progress push callback.

DownloadTaskState::snapshot_progress() -- the body behind progress() -- never
set speed_bps, per-segment speed_bps, or eta_seconds at all; only the
event-driven emit_progress_if_due() (which drives the on_progress callback)
computed them, from the same live SegWorker::speed_bps EMA seg_data()
maintains. snapshot_progress() now reads that same per-worker speed while
building its segment list, so a segment with no live worker (idle, paused,
complete, failed) correctly reports 0 and a segment with an active transfer
reports its real EMA, matching emit_progress_if_due()'s math including the
eta_seconds derivation.

engine_polled_progress_reports_nonzero_speed reproduces the bug (fails
without the fix, confirmed) by polling .progress() -- the same path DAEMON
uses -- during a throttled download and asserting speed_bps > 0 once real
progress has accumulated.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Q3QrF7rCt21bkAjt9BCDFQ
This commit is contained in:
2026-09-11 22:34:47 +04:00
co-authored by Claude Sonnet 5
parent d4ad48d494
commit ef58796d22
2 changed files with 53 additions and 4 deletions
+30
View File
@@ -457,3 +457,33 @@ VT_TEST(engine_content_length_mismatch_fails_honestly) {
VT_CHECK(rec.saw(EngineState::failed));
VT_CHECK_EQ(::access(td.file("clm.bin").c_str(), F_OK), -1); // never renamed into place
}
// --- DAEMON-reported bug: Progress.speed_bps reads 0 for the whole life of a live
// download while downloaded bytes visibly advance. DAEMON reads progress by polling
// DownloadHandle::progress() (engine_port_core.hpp), not the on_progress push callback --
// this exercises exactly that path. ---
VT_TEST(engine_polled_progress_reports_nonzero_speed) {
TestServer srv;
VT_REQUIRE(srv.available());
TmpDir td;
Recorder rec;
Engine eng;
auto h = eng.start(spec_for(srv, "/throttled/file/4M", td.file("sp.bin")), rec.cbs());
// Give it real, sustained progress: the speed estimate only updates on a >=0.5s
// sample window (seg_data), so a snapshot taken too early would legitimately read 0
// even with the bug fixed. Poll until downloaded has clearly advanced twice over.
std::uint64_t speed = 0;
for (int i = 0; i < 400 && speed == 0; ++i) {
std::this_thread::sleep_for(20ms);
auto p = h.progress();
if (p.downloaded >= 256u * 1024)
speed = p.speed_bps;
}
VT_CHECK(speed > 0);
h.cancel(/*discard_partial=*/true);
auto r = rec.wait();
VT_REQUIRE(!r.has_value());
}