daemon: D4b — download.pause/resume/start/cancel and queue.start/stop drive the scheduler
download.pause/resume/start/cancel and queue.start/stop were stubs; now they call into the scheduler and take effect immediately, not on the next 1s tick — pausing, resuming or cancelling a live transfer can't wait, and per ADR 0013 §3 the governor never touches a user-owned pause on its own. New rpc::TaskActionPort interface (owned by rpc/, implemented by sched::Scheduler) is what dispatcher.hpp depends on instead of sched/scheduler.hpp directly. Needed because veloxd_sched already links veloxd_rpc (for EventHub); dispatcher.hpp pulling in sched/scheduler.hpp directly would make it a real circular library dependency, breaking anything that links veloxd_rpc alone (cli's tests, as it turned out — hit and fixed during this change). Scheduler::user_pause/user_resume/user_start/user_cancel + pause_queue follow tick()'s existing to_pause pattern: call the engine (async, no synchronous effect) and transition the store eagerly so download.get/list are correct the instant the RPC call returns. Fixed a real bug surfaced while building this: transition() always overwrote pause_reason to NULL when the engine's own delayed pause-ack callback (on_state to paused, no error) arrived after whoever actually initiated the pause had already written the real reason — now it preserves the stored reason when the callback supplies none, instead of clobbering it. Covered by a regression check in sched_scheduler_test. store/queues gets get() and set_state() (was list()-only) for queue.start/stop. Verified against real veloxd + tools/testserver, not just unit tests: pausing a live single-segment throttled transfer freezes downloadedBytes, resume continues it from that point, cancel stops it; a bad taskId comes back in BulkTaskResult.failed with -32010, not a crash; queue.stop(pauseRunning:true) pauses the queue's running task immediately and queue.start resumes admission. Known gap: download.start's contract "a task in 'queued' jumps its queue" (priority bump) is not implemented — admission is still plain FIFO by created_at. Noted in deferrals.md. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
This commit is contained in:
+58
-25
@@ -1,11 +1,44 @@
|
||||
#include "store/queues.hpp"
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace velox::daemon::store {
|
||||
|
||||
namespace proto = velox::proto;
|
||||
|
||||
namespace {
|
||||
|
||||
// One queue row (columns queue_id, name, state, max_concurrent, schedule, in that order)
|
||||
// plus its member taskIds, read off the row a caller has already step()'d to.
|
||||
DbResult<proto::Queue> project_row(Db& db, Stmt& st) {
|
||||
proto::Queue q;
|
||||
q.queueId = st.column_text(0);
|
||||
q.name = st.column_text(1);
|
||||
if (auto s = proto::parse_QueueState(st.column_text(2))) q.state = *s;
|
||||
q.maxConcurrent = st.column_int(3);
|
||||
if (!st.column_is_null(4)) {
|
||||
auto j = nlohmann::json::parse(st.column_text(4), nullptr, false);
|
||||
if (auto sched = proto::parse<proto::Schedule>(j, "schedule")) q.schedule = *sched;
|
||||
}
|
||||
|
||||
auto ts = db.prepare("SELECT task_id FROM tasks WHERE queue_id = ?1 ORDER BY queue_position");
|
||||
if (!ts) return std::unexpected(ts.error());
|
||||
if (auto b = ts->bind(1, std::string_view(q.queueId)); !b) return std::unexpected(b.error());
|
||||
std::vector<std::string> ids;
|
||||
for (;;) {
|
||||
auto r = ts->step();
|
||||
if (!r) return std::unexpected(r.error());
|
||||
if (!*r) break;
|
||||
ids.push_back(ts->column_text(0));
|
||||
}
|
||||
q.taskIds = std::move(ids);
|
||||
return q;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DbResult<std::vector<proto::Queue>> Queues::list() {
|
||||
auto st = db_.prepare(
|
||||
"SELECT queue_id, name, state, max_concurrent, schedule FROM queues ORDER BY name");
|
||||
@@ -16,33 +49,33 @@ DbResult<std::vector<proto::Queue>> Queues::list() {
|
||||
auto row = st->step();
|
||||
if (!row) return std::unexpected(row.error());
|
||||
if (!*row) break;
|
||||
|
||||
proto::Queue q;
|
||||
q.queueId = st->column_text(0);
|
||||
q.name = st->column_text(1);
|
||||
if (auto s = proto::parse_QueueState(st->column_text(2))) q.state = *s;
|
||||
q.maxConcurrent = st->column_int(3);
|
||||
if (!st->column_is_null(4)) {
|
||||
auto j = nlohmann::json::parse(st->column_text(4), nullptr, false);
|
||||
if (auto sched = proto::parse<proto::Schedule>(j, "schedule")) q.schedule = *sched;
|
||||
}
|
||||
|
||||
auto ts = db_.prepare(
|
||||
"SELECT task_id FROM tasks WHERE queue_id = ?1 ORDER BY queue_position");
|
||||
if (!ts) return std::unexpected(ts.error());
|
||||
if (auto b = ts->bind(1, std::string_view(q.queueId)); !b) return std::unexpected(b.error());
|
||||
std::vector<std::string> ids;
|
||||
for (;;) {
|
||||
auto r = ts->step();
|
||||
if (!r) return std::unexpected(r.error());
|
||||
if (!*r) break;
|
||||
ids.push_back(ts->column_text(0));
|
||||
}
|
||||
q.taskIds = std::move(ids);
|
||||
|
||||
out.push_back(std::move(q));
|
||||
auto q = project_row(db_, *st);
|
||||
if (!q) return std::unexpected(q.error());
|
||||
out.push_back(std::move(*q));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
DbResult<std::optional<proto::Queue>> Queues::get(std::string_view queue_id) {
|
||||
auto st = db_.prepare(
|
||||
"SELECT queue_id, name, state, max_concurrent, schedule FROM queues WHERE queue_id = ?1");
|
||||
if (!st) return std::unexpected(st.error());
|
||||
if (auto b = st->bind(1, queue_id); !b) return std::unexpected(b.error());
|
||||
auto row = st->step();
|
||||
if (!row) return std::unexpected(row.error());
|
||||
if (!*row) return std::optional<proto::Queue>{};
|
||||
auto q = project_row(db_, *st);
|
||||
if (!q) return std::unexpected(q.error());
|
||||
return std::optional<proto::Queue>{std::move(*q)};
|
||||
}
|
||||
|
||||
DbResult<bool> Queues::set_state(std::string_view queue_id, std::string_view state) {
|
||||
auto st = db_.prepare("UPDATE queues SET state = ?2 WHERE queue_id = ?1");
|
||||
if (!st) return std::unexpected(st.error());
|
||||
if (auto b = st->bind(1, queue_id); !b) return std::unexpected(b.error());
|
||||
if (auto b = st->bind(2, state); !b) return std::unexpected(b.error());
|
||||
if (auto r = st->step(); !r) return std::unexpected(r.error());
|
||||
return sqlite3_changes(db_.raw()) > 0;
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::store
|
||||
|
||||
Reference in New Issue
Block a user