rate/token_bucket.hpp — a lazily-refilled TokenBucket (starts full: burst
then throttle, IDM behaviour; rate 0 == unlimited; burst caps idle
accumulation) and RateLimiter, the global -> per-queue -> per-task
hierarchy (docs/04 §6). acquire(task, n) peeks every applicable level and
commits on all-or-none so a blocked attempt never leaks tokens at a level
that had them; held under one mutex so a concurrent detach can't dangle
the bucket it's using. vdm/ids.hpp gains QueueId.
Tests: burst/refill/cap/unlimited for the bucket; tightest-level-binds,
no-partial-consumption, detach-safety, and an 8-thread aggregate-rate
check for the hierarchy. Green under ASan/UBSan and TSan.
Engine-API review (DAEMON signed off, no sched/ or dispatch rewrite):
- Engine::rate_limiter() accessor added (limiter.set -> set_global_limit).
- Checksum::Algo gains sha512 to match the wire Checksum set.
- DownloadSpec: DAEMON creates save_path's parent dir before start();
missing dir -> Error::path_rejected (made explicit).
- cancel(): documented to fire on_state(_, cancelled, nullopt) then
on_finished(Err{canceled}), in that order; download.cancel ==
cancel(false), download.remove == cancel(true).
- engine-api-m1.md: the five open questions resolved with DAEMON's
answers (probe_hint optional, single cancel flag, {restart,
keep_partial, abort} is the whole set, per-task 4 Hz is fine,
refresh_url restarts all segments after a validating re-probe).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
83 lines
3.8 KiB
C++
83 lines
3.8 KiB
C++
// vdm/engine.hpp — the download engine's single entry point. REVIEW SKETCH (stage 7
|
|
// pre-work); bodies land in stage 8. See core/docs/engine-api-m1.md.
|
|
//
|
|
// The engine owns the HTTP client, the probe pool, the segment budget, and the disk I/O.
|
|
// Its input is a DownloadSpec; its output is bytes at save_path plus typed callbacks. No
|
|
// JSON, no SQL, no Qt, no RPC — DAEMON projects the callbacks onto the wire contract.
|
|
//
|
|
// This header compiles standalone.
|
|
|
|
#ifndef VDM_ENGINE_HPP
|
|
#define VDM_ENGINE_HPP
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
|
|
#include "vdm/rate/token_bucket.hpp"
|
|
#include "vdm/segment/budget.hpp"
|
|
#include "vdm/task/download.hpp"
|
|
|
|
namespace vdm {
|
|
|
|
class Engine {
|
|
public:
|
|
struct Config {
|
|
// Defaults used when a DownloadSpec leaves the field unset. Live-adjustable via
|
|
// the setters below (they take effect on the next segment (re)assignment, not by
|
|
// resizing an in-flight buffer).
|
|
std::uint32_t default_segments = 8; // connection.maxSegmentsPerDownload
|
|
std::uint64_t default_buffer_bytes = 1u << 20; // connection.bufferBytes (1 MiB)
|
|
std::uint64_t min_segment_bytes = 1u << 20; // never split below this
|
|
std::uint64_t max_total_buffer_bytes = 128ull << 20; // connection.maxTotalBufferBytes
|
|
std::uint32_t max_active_segments = 32; // connection.maxActiveSegments
|
|
std::uint32_t probe_pool_size = 4; // ADR 0011 §5, outside the budget
|
|
long default_max_retries = 10; // per segment
|
|
std::uint32_t http_workers = 0; // 0 => hardware-derived (<=4)
|
|
};
|
|
|
|
Engine(); // default Config
|
|
explicit Engine(Config cfg);
|
|
~Engine(); // cancels every running task and joins before returning
|
|
|
|
Engine(const Engine &) = delete;
|
|
Engine &operator=(const Engine &) = delete;
|
|
|
|
// Start a download. Returns immediately with a handle; the task begins in `probing`
|
|
// (or `connecting` when spec.probe_hint is supplied). Every failure — bad URL, DNS,
|
|
// an unwritable save_path — is delivered through callbacks.on_finished, never thrown.
|
|
[[nodiscard]] task::DownloadHandle start(task::DownloadSpec spec,
|
|
task::DownloadCallbacks callbacks);
|
|
|
|
// The global segment allocator. DAEMON's scheduler drives admission through this
|
|
// (set_max_active_segments / set_host_segment_cap / set_task_order) and reads
|
|
// occupancy from it (budget() / segments_active() / starved_tasks() /
|
|
// on_budget_changed). See ADR 0011.
|
|
[[nodiscard]] segment::SegmentBudget &segment_budget() noexcept;
|
|
|
|
// The hierarchical speed limiter (docs/04 §6): global -> per-queue -> per-task token
|
|
// buckets. `limiter.set {globalBps, enabled}` -> rate_limiter().set_global_limit();
|
|
// per-queue / per-task limits and the task<->queue attachment come from DAEMON too.
|
|
// The engine paces every segment read through it.
|
|
[[nodiscard]] rate::RateLimiter &rate_limiter() noexcept;
|
|
|
|
// Live settings (connection.* changes from settings.set). Each affects future work.
|
|
void set_default_segments(std::uint32_t n);
|
|
void set_default_buffer_bytes(std::uint64_t bytes);
|
|
void set_max_total_buffer_bytes(std::uint64_t bytes);
|
|
void set_probe_pool_size(std::uint32_t n);
|
|
|
|
// A standalone probe for the File Info dialog, on the same pool as spec-less probes
|
|
// (never charged against the segment budget). capture.offer's 750 ms deadline is
|
|
// DAEMON's to enforce — it should answer `ignore` and probe after, never block on
|
|
// this.
|
|
void probe(net::ProbeRequest req, std::function<void(Result<net::ProbeResult>)> done);
|
|
|
|
private:
|
|
struct Impl;
|
|
std::unique_ptr<Impl> impl_;
|
|
};
|
|
|
|
} // namespace vdm
|
|
|
|
#endif // VDM_ENGINE_HPP
|