From 3a22e40225848b6ddc0f7e31562493dc4db060a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?=
Date: Fri, 23 Jan 2026 20:20:55 +0100
Subject: [PATCH] Fix duration subtraction panic in remaining build time
estimation
---
src/server/mod.rs | 26 +++++++++++++++-----------
src/templates.rs | 2 +-
web/templates/queue.html | 6 +++---
3 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/src/server/mod.rs b/src/server/mod.rs
index c06698c0..f08c1187 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -357,21 +357,24 @@ pub async fn queue_handler(
None => None,
};
- let average_auto_duration = {
+ let average_build_duration = {
let total_duration = last_ten_builds
.iter()
.filter_map(|build| build.duration)
.map(|d| d.0)
.sum::();
+ let count = last_ten_builds.len() as u32;
let total_duration = if total_duration.is_zero() {
- // Default guess of 3 hours
- Duration::from_secs(3600 * 3)
+ // Default guess of 3 hours per build
+ Duration::from_secs(3600 * 3) * count
} else {
total_duration
};
- let count = last_ten_builds.len() as u32;
- let count = if count > 0 { count } else { 1 };
- total_duration / count
+ if count > 1 {
+ total_duration / count
+ } else {
+ total_duration
+ }
};
let mut in_queue_count = 0;
@@ -394,18 +397,18 @@ pub async fn queue_handler(
match &status {
QueueStatus::Pending(_, _) => {
// Try to guess already elapsed time of the pending workflow
- let subtract_duration = if let Some(workflow) = &pending_workflow {
+ let elapsed = if let Some(workflow) = &pending_workflow {
(Utc::now() - workflow.created_at)
.to_std()
.unwrap_or_default()
} else {
Duration::ZERO
};
- in_queue.insert(pr.number, average_auto_duration - subtract_duration);
+ in_queue.insert(pr.number, average_build_duration.saturating_sub(elapsed));
}
// For an approved PR, assume that it will take the average auto build duration
QueueStatus::Approved(_) => {
- in_queue.insert(pr.number, average_auto_duration);
+ in_queue.insert(pr.number, average_build_duration);
}
QueueStatus::Failed(_, _)
| QueueStatus::ReadyForMerge(_, _)
@@ -414,7 +417,7 @@ pub async fn queue_handler(
}
}
- let mut expected_remaining_duration = Duration::ZERO;
+ let mut expected_remaining_duration: Option = None;
// Rollup members whose rollup is in the queue, and thus its duration will be counted
let rollup_members: HashSet = rollups
@@ -429,7 +432,8 @@ pub async fn queue_handler(
if rollup_members.contains(&pr) {
continue;
}
- expected_remaining_duration += remaining_duration;
+ expected_remaining_duration =
+ Some(expected_remaining_duration.unwrap_or_default() + remaining_duration);
}
Ok(HtmlTemplate(QueueTemplate {
diff --git a/src/templates.rs b/src/templates.rs
index 82ce4fc8..330a74a8 100644
--- a/src/templates.rs
+++ b/src/templates.rs
@@ -87,7 +87,7 @@ pub struct QueueTemplate {
// Active workflow for an active pending auto build
pub pending_workflow: Option,
// Guesstimated duration to merge all current approved/pending PRs in the queue
- pub expected_remaining_duration: Duration,
+ pub expected_remaining_duration: Option,
}
impl QueueTemplate {
diff --git a/web/templates/queue.html b/web/templates/queue.html
index 541dc816..28d281b4 100644
--- a/web/templates/queue.html
+++ b/web/templates/queue.html
@@ -192,11 +192,11 @@
{{ stats.total_count }} total, {{ stats.in_queue_count }} in queue,
{{ stats.failed_count }} failed
- {%- if !expected_remaining_duration.is_zero() -%}
+ {%- if let Some(expected) = expected_remaining_duration -%}
|
- Estimated time to merge queue: {{ format_duration(*expected_remaining_duration) }}
+ title="Estimated duration to merge all current PRs in the queue, assuming that there are no build failures.">
+ Estimated time to flush queue: {{ format_duration(**expected) }}
{%- endif -%}