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
65 lines
3.0 KiB
C++
65 lines
3.0 KiB
C++
#pragma once
|
|
|
|
// The seam between the Scheduler and CORE's engine. Everything the Scheduler drives on the
|
|
// engine goes through this interface, so the Scheduler is unit-testable without a live
|
|
// engine and the daemon is not bound to the concrete `vdm::Engine`. The real
|
|
// implementation (engine_port_core, added when velox::core's stage-8 bodies reach main)
|
|
// wraps `vdm::Engine` + `vdm::segment::SegmentBudget`; FakeEnginePort records calls.
|
|
//
|
|
// Task ids here are `vdm::TaskId` — the engine assigns one from start() and the Scheduler
|
|
// keeps the wire-UUID <-> TaskId map (ADR 0013). Admission is the Scheduler's: for a task
|
|
// the governor admits, it probes first (persisting sizeBytes/resumable/validator before a
|
|
// single byte moves), then calls start() with that ProbeResult as probe_hint. The min-1
|
|
// fairness rule in SegmentBudget then guarantees each started task a slot; set_task_order
|
|
// pushes the priority.
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "vdm/ids.hpp"
|
|
#include "vdm/net/probe.hpp"
|
|
#include "vdm/task/download.hpp"
|
|
#include "vdm/util/result.hpp"
|
|
|
|
namespace velox::daemon::sched {
|
|
|
|
class EnginePort {
|
|
public:
|
|
virtual ~EnginePort() = default;
|
|
|
|
// Runs on the probe pool, outside the segment budget (ADR 0011 §5); `done` arrives on
|
|
// an engine thread, exactly once. The Scheduler probes before every start() so it
|
|
// always has a real ProbeResult (size, resumable, validator) to persist and to pass
|
|
// back as DownloadSpec.probe_hint — one code path instead of "sometimes has one".
|
|
virtual void probe(const vdm::net::ProbeRequest& req,
|
|
std::function<void(vdm::Result<vdm::net::ProbeResult>)> done) = 0;
|
|
|
|
virtual vdm::TaskId start(const vdm::task::DownloadSpec& spec,
|
|
vdm::task::DownloadCallbacks callbacks) = 0;
|
|
|
|
virtual void pause(vdm::TaskId) = 0;
|
|
virtual void resume(vdm::TaskId) = 0;
|
|
virtual void cancel(vdm::TaskId, bool discard_partial) = 0;
|
|
virtual void provide_auth(vdm::TaskId, const std::string& username,
|
|
const std::string& password, bool remember) = 0;
|
|
virtual void decide(vdm::TaskId, vdm::task::Decision) = 0;
|
|
virtual void refresh_url(vdm::TaskId, const std::string& url) = 0;
|
|
|
|
// The daemon is done with this task (it went terminal). Drop the handle. Idempotent.
|
|
virtual void release(vdm::TaskId) = 0;
|
|
|
|
// A synchronous, lock-guarded snapshot (DownloadHandle::progress()). nullopt if the
|
|
// id is unknown (already released, or never started).
|
|
virtual std::optional<vdm::task::Progress> progress(vdm::TaskId) const = 0;
|
|
|
|
// ADR 0011 admission surface. Values are DAEMON's; enforcement is the engine's.
|
|
virtual void set_task_order(const std::vector<vdm::TaskId>& order) = 0;
|
|
virtual void set_max_active_segments(std::uint32_t n) = 0;
|
|
virtual void set_host_segment_cap(const std::string& host, std::uint32_t cap) = 0;
|
|
};
|
|
|
|
} // namespace velox::daemon::sched
|