fix: count retry credits

This commit is contained in:
zhom committed 2026-11-01 16:27:48 +04:00
1 parent a6a0c33afd
commit bf25b39dff
3 files changed
+71 -13

No files matched your search

+23 -1
View File
@@ -155,7 +155,7 @@ impl Batch {
pub fn tally(&self) -> Tally {
let mut tally = Tally::default();
for task in &self.tasks {
tally.credits_used += task.credits_used;
tally.credits_used += task.total_credits();
match task.status {
TaskStatus::Queued => tally.queued += 1,
TaskStatus::Done => tally.done += 1,
@@ -275,4 +275,26 @@ mod tests {
assert_eq!(batch.tasks[1].status, TaskStatus::Queued);
assert_eq!(batch.take_next(), Some(1));
}
#[test]
fn a_retry_keeps_the_credits_of_every_attempt() {
let mut batch = batch(2, 2);
batch.tasks[0].status = TaskStatus::Done;
batch.tasks[0].credits_used = 10;
batch.tasks[1].fail("AGENT_BUSY".into(), 1);
batch.tasks[1].credits_used = 7;
assert_eq!(batch.retry_failed(), 1);
assert_eq!(
batch.tasks[1].credits_used, 0,
"the new attempt starts at zero"
);
assert_eq!(batch.tasks[1].total_credits(), 7);
assert_eq!(batch.tally().credits_used, 17);
batch.tasks[1].credits_used = 4;
batch.tasks[1].fail("AGENT_BUSY".into(), 2);
batch.retry_failed();
batch.tasks[1].credits_used = 3;
assert_eq!(batch.tasks[1].total_credits(), 14);
assert_eq!(batch.tally().credits_used, 24);
}
}
+35 -11
View File
@@ -153,7 +153,7 @@ impl From<&Task> for TaskView {
approval: task.approval.clone(),
result: task.result.clone(),
error: task.error.clone(),
credits_used: task.credits_used,
credits_used: task.total_credits(),
started_at: task.started_at,
finished_at: task.finished_at,
}
@@ -379,15 +379,10 @@ fn finalize(batch_id: &str, index: usize) {
task.profile_id,
task.task_id.as_deref().unwrap_or("-"),
task.status.run_status(),
task.credits_used
task.total_credits()
);
if let Some(run_id) = task.run_id.clone() {
let patch = cloud::run_patch(
task.status.run_status(),
task.result.as_deref(),
task.credits_used,
task.error.as_deref(),
);
let patch = final_report(&task);
tokio::spawn(async move { cloud::report_run(&run_id, patch).await });
}
if task.launched_here && headless {
@@ -397,6 +392,16 @@ fn finalize(batch_id: &str, index: usize) {
waker.notify_one();
}
/// What the cloud records when a task ends: every attempt's credits count.
fn final_report(task: &Task) -> Value {
cloud::run_patch(
task.status.run_status(),
task.result.as_deref(),
task.total_credits(),
task.error.as_deref(),
)
}
fn load_profile(profile_id: &str) -> Result<crate::profile::BrowserProfile, String> {
crate::profile::ProfileManager::instance()
.list_profiles()
@@ -554,14 +559,15 @@ async fn start_task(batch_id: String, index: usize, profile_id: String, order: O
let run = task
.run_id
.clone()
.filter(|_| !finished && !task.status.is_final());
.filter(|_| !finished && !task.status.is_final())
.map(|run_id| (run_id, task.total_credits()));
publish(batch);
(finished, run)
};
match finished {
(true, _) => finalize(&batch_id, index),
(false, Some(run_id)) => {
cloud::report_run(&run_id, cloud::run_patch("running", None, 0, None)).await;
(false, Some((run_id, spent))) => {
cloud::report_run(&run_id, cloud::run_patch("running", None, spent, None)).await;
}
_ => {}
}
@@ -1121,6 +1127,24 @@ pub async fn run_agent_routine(
mod tests {
use super::*;
#[test]
fn the_final_report_counts_the_credits_of_every_attempt() {
let mut task = Task::queued("p1".into());
task.run_id = Some("r1".into());
task.credits_used = 12;
task.fail("AGENT_BUSY".into(), 1);
task.requeue();
assert_eq!(task.run_id.as_deref(), Some("r1"));
task.credits_used = 30;
task.status = TaskStatus::Done;
task.result = Some("Found it".into());
assert_eq!(
final_report(&task),
json!({ "status": "done", "creditsUsed": 42, "summary": "Found it" })
);
assert_eq!(TaskView::from(&task).credits_used, 42);
}
#[test]
fn a_start_carries_only_what_was_asked() {
let order = Order {
+13 -1
View File
@@ -167,7 +167,10 @@ pub struct Task {
pub result: Option<String>,
/// A backend error code, or the engine's own words when it failed.
pub error: Option<String>,
/// Credits of this attempt, as the engine reports them.
pub credits_used: u64,
/// Credits of the earlier attempts: a retry spends them again, never back.
pub credits_before: u64,
pub started_at: Option<u64>,
pub finished_at: Option<u64>,
/// Donut started this browser for the task, so it stops it after.
@@ -187,17 +190,26 @@ impl Task {
result: None,
error: None,
credits_used: 0,
credits_before: 0,
started_at: None,
finished_at: None,
launched_here: false,
}
}
/// Back to the queue for another attempt; the run record stays.
/// Back to the queue for another attempt; the run record and the credits
/// spent so far stay.
pub fn requeue(&mut self) {
let run_id = self.run_id.take();
let spent = self.total_credits();
*self = Self::queued(std::mem::take(&mut self.profile_id));
self.run_id = run_id;
self.credits_before = spent;
}
/// Credits of every attempt.
pub fn total_credits(&self) -> u64 {
self.credits_before + self.credits_used
}
pub fn fail(&mut self, code: String, now_ms: u64) {