The scheduling brain, built against CORE's headers (vdm/engine.hpp,
vdm/segment/budget.hpp) — signatures only; the Engine bodies land in
CORE stage 8 and the Scheduler that wires governor <-> store <-> engine
<-> timer comes after that (daemon/docs/deferrals.md D4).
- sched/governor — a pure decision function. In: a snapshot of every
task's coarse RunState and every queue's state (schedule windows
pre-resolved). Out: {to_start, to_resume, to_pause, pause_reasons,
priority_order}. Enforces, all in TASK units per ADR 0011 §1:
connection.maxConcurrentDownloads; the min(that, maxActiveSegments)
clamp (§2); Queue.maxConcurrent; the per-host task cap (§4); and a
stopped queue / closed window runs nothing. Never touches a task
paused for `user` or CORE's `auto` (ADR 0013 §3) — only Schedule /
QueueStopped / AdmissionReconcile are auto-resumable. Deterministic:
main-list before queued-in-queue, then queue order, then FIFO, then
task_id.
- sched/schedule_window — window_open(Schedule, local tm): disabled =>
always open; `once` => date + time match; `periodic` => weekday in
daysOfWeek (empty = every day) + time in [start, stop); null start =>
midnight, null stop => end of day, stop < start => overnight window.
Pure; re-evaluated every tick, no cached instants.
- veloxd_sched static lib; veloxd links it (nothing calls it yet).
Tests (ASan+UBSan and TSan clean): veloxd.sched_window (10 window
cases incl. overnight, once, null bounds), veloxd.sched_governor
(global/clamp/per-queue/per-host caps, stop vs window pause reasons,
resume-only-governor-reasons, auth-pause untouched, admission
reconcile, determinism under shuffled input). 32 daemon/cli tests
green; full tree green.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
121 lines
4.1 KiB
C++
121 lines
4.1 KiB
C++
#include "sched/schedule_window.hpp"
|
|
|
|
#include <cstring>
|
|
#include <ctime>
|
|
|
|
#include "check.hpp"
|
|
|
|
namespace proto = velox::proto;
|
|
using velox::daemon::sched::window_open;
|
|
|
|
namespace {
|
|
|
|
// A std::tm for a given weekday (0=Sun) and HH:MM on 2026-09-14 (a Monday) + offset days.
|
|
std::tm at(int wday, int hh, int mm, const char* date = "2026-09-14") {
|
|
std::tm t{};
|
|
t.tm_year = 126; // 2026
|
|
// parse "YYYY-MM-DD"
|
|
int y = 0, mo = 0, d = 0;
|
|
std::sscanf(date, "%d-%d-%d", &y, &mo, &d);
|
|
t.tm_year = y - 1900;
|
|
t.tm_mon = mo - 1;
|
|
t.tm_mday = d;
|
|
t.tm_wday = wday;
|
|
t.tm_hour = hh;
|
|
t.tm_min = mm;
|
|
return t;
|
|
}
|
|
|
|
proto::Schedule periodic(const char* start, const char* stop, std::vector<std::int64_t> days) {
|
|
proto::Schedule s;
|
|
s.enabled = true;
|
|
s.mode = proto::ScheduleMode::Periodic;
|
|
if (start) s.startTime = start;
|
|
if (stop) s.stopTime = stop;
|
|
if (!days.empty()) s.daysOfWeek = std::move(days);
|
|
return s;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void run() {
|
|
// --- disabled schedule: always open --------------------------------------------
|
|
{
|
|
proto::Schedule s;
|
|
s.enabled = false;
|
|
s.mode = proto::ScheduleMode::Periodic;
|
|
CHECK(window_open(s, at(3, 3, 0)));
|
|
std::optional<proto::Schedule> none;
|
|
CHECK(window_open(none, at(3, 3, 0)));
|
|
}
|
|
|
|
// --- periodic, weekdays 01:00-06:00 -------------------------------------------
|
|
{
|
|
const auto s = periodic("01:00", "06:00", {1, 2, 3, 4, 5}); // Mon-Fri
|
|
CHECK(window_open(s, at(1, 2, 30))); // Monday 02:30 -> open
|
|
CHECK(!window_open(s, at(1, 6, 0))); // 06:00 is the exclusive end
|
|
CHECK(!window_open(s, at(1, 0, 59))); // before start
|
|
CHECK(!window_open(s, at(0, 2, 30))); // Sunday -> wrong day
|
|
CHECK(!window_open(s, at(6, 2, 30))); // Saturday -> wrong day
|
|
}
|
|
|
|
// --- periodic, no daysOfWeek -> every day ------------------------------------
|
|
{
|
|
const auto s = periodic("09:00", "17:00", {});
|
|
CHECK(window_open(s, at(0, 12, 0))); // Sunday still fine
|
|
CHECK(window_open(s, at(3, 9, 0))); // inclusive start
|
|
CHECK(!window_open(s, at(3, 17, 0)));
|
|
}
|
|
|
|
// --- overnight window 22:00-06:00 ------------------------------------------
|
|
{
|
|
const auto s = periodic("22:00", "06:00", {});
|
|
CHECK(window_open(s, at(3, 23, 0))); // late evening
|
|
CHECK(window_open(s, at(3, 2, 0))); // early morning
|
|
CHECK(!window_open(s, at(3, 12, 0))); // midday -> closed
|
|
CHECK(window_open(s, at(3, 22, 0))); // inclusive start
|
|
CHECK(!window_open(s, at(3, 6, 0))); // exclusive end
|
|
}
|
|
|
|
// --- null stopTime -> until end of day ----------------------------------
|
|
{
|
|
auto s = periodic("20:00", nullptr, {});
|
|
CHECK(!window_open(s, at(3, 19, 59)));
|
|
CHECK(window_open(s, at(3, 20, 0)));
|
|
CHECK(window_open(s, at(3, 23, 59)));
|
|
}
|
|
|
|
// --- null startTime -> from midnight -----------------------------------
|
|
{
|
|
auto s = periodic(nullptr, "08:00", {});
|
|
CHECK(window_open(s, at(3, 0, 0)));
|
|
CHECK(window_open(s, at(3, 7, 59)));
|
|
CHECK(!window_open(s, at(3, 8, 0)));
|
|
}
|
|
|
|
// --- mode "once": only on the named date ------------------------------
|
|
{
|
|
proto::Schedule s;
|
|
s.enabled = true;
|
|
s.mode = proto::ScheduleMode::Once;
|
|
s.startTime = "10:00";
|
|
s.stopTime = "12:00";
|
|
s.onceDate = "2026-09-14";
|
|
CHECK(window_open(s, at(1, 11, 0, "2026-09-14"))); // right date + time
|
|
CHECK(!window_open(s, at(1, 9, 0, "2026-09-14"))); // right date, before window
|
|
CHECK(!window_open(s, at(2, 11, 0, "2026-09-15"))); // wrong date
|
|
s.daysOfWeek = {2}; // ignored for "once"
|
|
CHECK(window_open(s, at(1, 11, 0, "2026-09-14")));
|
|
}
|
|
|
|
// --- "once" with no onceDate never runs ------------------------------
|
|
{
|
|
proto::Schedule s;
|
|
s.enabled = true;
|
|
s.mode = proto::ScheduleMode::Once;
|
|
CHECK(!window_open(s, at(1, 11, 0)));
|
|
}
|
|
}
|
|
|
|
TEST_MAIN()
|