daemon: persist engine progress/probe to the store — segments:0 no longer leaks
Engine numbers never reached the store: download.get after a correctly-finished
download reported sizeBytes: null, downloadedBytes: 0, speedBps: 0, resumable:
false, segments: 0, segmentDetail: [] — segments: 0 breaks the frozen contract
(TaskSummary.segments is minimum:1, required).
- Scheduler::tick() now probes (EnginePort::probe) before every start(), persisting
sizeBytes/resumable/validators via Tasks::set_probe_result before a byte moves,
then starts the engine with that ProbeResult as probe_hint.
- Scheduler::persist_progress() (new) writes downloadedBytes/speedBps/segments/
segmentDetail from the engine's Progress. Called from progress_snapshot() (the
250ms tick) *and* once more from on_engine_state right before release()/unmap on
every terminal transition, so a task that finishes between two ticks — the common
case for anything small or fast — still leaves real numbers instead of the
pre-persistence defaults.
- TaskSummary.segments is sourced from segments.size() when the task has any
(matching what actually lands in segmentDetail, per the schema's "exactly
segments entries"), falling back to the engine's effective_segments (budget
slots *held*, not necessarily physical range count) only pre-segmentation.
- Tasks::set_final_bytes tops up on_finished's byte count as a last-resort
backstop.
- store/segments.{cpp,hpp}: read/write access to the segments table behind
TaskDetail.segmentDetail. Wired into daemon/CMakeLists.txt.
- migrations/0002: speed_bps on tasks and segments; fixes segments.state's CHECK
to include 'pending' (0001 omitted it, so a pre-connect snapshot could never be
written).
- store_migrations_test's forward-only loop faked "released version N" by setting
the user_version pragma alone, with no real schema underneath — never exercised
until 0002 existed. Fixed to actually build the db through migrations 1..N first.
Verified against real veloxd + tools/testserver (not just unit tests):
download.list/download.get correct immediately after completion and after a
daemon restart, with saveTo.allowedRoots pointed at an isolated dir.
Observed but not fixed (CORE, not this lane, noted in deferrals.md): Progress.
speed_bps reads back 0 for the whole lifetime of a live throttled download in the
same E2E check, despite downloadedBytes visibly advancing. DAEMON passes it
through unmodified; filed rather than worked around.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
This commit is contained in:
@@ -39,6 +39,7 @@ struct TaskRow {
|
||||
|
||||
std::optional<std::int64_t> size_bytes;
|
||||
std::int64_t downloaded_bytes = 0;
|
||||
std::int64_t speed_bps = 0;
|
||||
bool resumable = false;
|
||||
|
||||
std::optional<std::int64_t> req_segments;
|
||||
@@ -81,7 +82,26 @@ public:
|
||||
// Byte-counter update from an engine progress tick — cheaper than a full row rewrite,
|
||||
// and keeps download.list / download.get current between state transitions.
|
||||
DbResult<bool> update_progress(std::string_view task_id, std::int64_t downloaded_bytes,
|
||||
std::int64_t eff_segments, std::int64_t eff_buffer_bytes);
|
||||
std::int64_t speed_bps, std::int64_t eff_segments,
|
||||
std::int64_t eff_buffer_bytes);
|
||||
|
||||
// What the probe learned, persisted before start() so a task that completes before any
|
||||
// progress tick still reports a real sizeBytes / resumable (not the pre-probe default).
|
||||
struct ProbeFields {
|
||||
std::optional<std::int64_t> size_bytes;
|
||||
bool resumable = false;
|
||||
std::optional<std::string> etag;
|
||||
std::optional<std::string> last_modified;
|
||||
std::optional<std::string> content_type;
|
||||
std::optional<std::string> effective_url;
|
||||
};
|
||||
DbResult<bool> set_probe_result(std::string_view task_id, const ProbeFields& fields);
|
||||
|
||||
// on_finished's byte count, for a task that completes before any progress tick ever
|
||||
// ran (see AGENT-DAEMON review: the bug this closes). size_bytes is only filled in if
|
||||
// still unset — the probe's total_size is the more authoritative source when both
|
||||
// exist and happen to disagree (a chunked source with no declared length, say).
|
||||
DbResult<bool> set_final_bytes(std::string_view task_id, std::int64_t bytes);
|
||||
|
||||
private:
|
||||
Db& db_;
|
||||
|
||||
Reference in New Issue
Block a user