Stage 8 of the CORE build order: the bodies behind the DownloadSpec / callback API reviewed in core/docs/engine-api-m1.md. Wires probe -> segment workers -> WriteBuffer -> SparseFile -> .veloxpart.meta -> retry/backoff -> SegmentBudget -> RateLimiter -> callbacks into one event-driven machine. - Engine (src/engine.cpp): owns HttpClient, Prober, SegmentBudget, RateLimiter and one timer jthread (min-heap of scheduled fns). start() builds a task and returns a DownloadHandle; ~Impl quiesces every task before joining the timer so no callback fires during teardown. - DownloadTaskState (src/task/download_task.cpp): one `mu` task lock; a shared_mutex over the worker map for the curl write path; callbacks collected under `mu` and fired after release via a separate deferred queue; weak_from_this() in every async hop. State machine over the CORE-owned EngineState subset, auto-pause on 401/407 and on a 200 where 206 was expected, validated resume via If-Range. - digest (src/task/digest.cpp): OpenSSL EVP hash_file() for the optional post-download checksum; links OpenSSL::Crypto PRIVATE. - Segmenter::release_segment(): hand a paused segment back to the pool unassigned so resume's assign_slot() picks it up instead of splitting a still-"assigned" range and orphaning its front half. - DownloadHandle now names the real control block (vdm::task:: DownloadTaskState, defined only in the engine TU) via a namespace-scope fwd decl and a public-but-effectively-engine-only ctor, replacing the nested State/friend pair. Every public signature is unchanged; DAEMON (vdm-79) confirmed sched/ names only the public API. Fixes found while building the end-to-end suite (tests/task/engine_test.cpp, 9 cases against tools/testserver, green under ASan/UBSan and TSan): - a dropped connection lost its unflushed WriteBuffer tail while advance() had already counted those bytes as done -> a retry resumed past an unwritten hole. Flush on the failure path. - when the byte counters hit total while other workers were still live, teardown dropped their buffered tails. Now: cancel them and let each worker's own seg_finished drain it (the `assembling` state), last one starts verification -- no cross-thread buffer access. - seg_head() let a 401 with credentials present abort before libcurl's resend; now it proceeds once and acts on the final status. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
59 lines
2.1 KiB
C++
59 lines
2.1 KiB
C++
// vdm/task/download_task.hpp — internal: the task machine behind DownloadHandle, and the
|
|
// narrow interface it uses to reach engine-owned resources (so this TU doesn't depend on
|
|
// Engine::Impl).
|
|
|
|
#ifndef VDM_TASK_DOWNLOAD_TASK_HPP
|
|
#define VDM_TASK_DOWNLOAD_TASK_HPP
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
|
|
#include "vdm/engine.hpp"
|
|
#include "vdm/ids.hpp"
|
|
#include "vdm/net/http_client.hpp"
|
|
#include "vdm/net/probe.hpp"
|
|
#include "vdm/rate/token_bucket.hpp"
|
|
#include "vdm/segment/budget.hpp"
|
|
#include "vdm/task/download.hpp"
|
|
|
|
namespace vdm::task {
|
|
|
|
using TimerId = std::uint64_t;
|
|
|
|
// Implemented by Engine::Impl. Every method is safe to call from any thread.
|
|
struct TaskHost {
|
|
virtual ~TaskHost() = default;
|
|
|
|
virtual net::HttpClient &http() = 0;
|
|
virtual segment::SegmentBudget &budget() = 0;
|
|
virtual rate::RateLimiter &limiter() = 0;
|
|
virtual const Engine::Config &config() = 0;
|
|
|
|
// One-shot timer. `fn` runs on the engine's timer thread. cancel_timer is a no-op if
|
|
// it already fired or never existed.
|
|
virtual TimerId schedule(SteadyTime at, std::function<void()> fn) = 0;
|
|
virtual void cancel_timer(TimerId id) = 0;
|
|
|
|
// Probe pool, outside the segment budget (ADR 0011 §5).
|
|
virtual void probe(net::ProbeRequest req,
|
|
std::function<void(Result<net::ProbeResult>)> done) = 0;
|
|
|
|
// The task reached a terminal state — drop it from the engine's registry.
|
|
virtual void task_retired(TaskId id) = 0;
|
|
};
|
|
|
|
// Create a task and begin it (probe or connect). The returned control block is what
|
|
// DownloadHandle wraps (DownloadHandle{state}); the engine keeps its own copy so the task
|
|
// outlives a caller that drops its handle.
|
|
[[nodiscard]] std::shared_ptr<DownloadTaskState> create_task(TaskHost &host, TaskId id,
|
|
DownloadSpec spec,
|
|
DownloadCallbacks cbs);
|
|
|
|
// Engine shutdown: stop every transfer and fire no further callbacks. Safe on nullptr.
|
|
void quiesce_task(const std::shared_ptr<DownloadTaskState> &s);
|
|
|
|
} // namespace vdm::task
|
|
|
|
#endif // VDM_TASK_DOWNLOAD_TASK_HPP
|