Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.

Commit fae6823

Browse files
authored
feat(orion-server): queue builds when no idle worker for up to 60m (#2163)
* feat(orion-server): queue builds when no idle worker for up to 60m Restore FIFO enqueue when VMs are offline or busy instead of immediately marking builds Interrupted, and re-queue on dispatch races. * feat(cl): show Orion build status on CL list Return aggregated latest-task build_status from the list API and render it with the shared Checks status icons.
1 parent f996ec3 commit fae6823

21 files changed

Lines changed: 594 additions & 297 deletions

File tree

‎Cargo.lock‎

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ dashmap = "6.2.1"
111111
once_cell = "1.21.4"
112112
serial_test = "4.0.1"
113113
sysinfo = "0.39.6"
114-
http = "1.4.2"
114+
http = "1.5.0"
115115
url = "2.5.8"
116116
jemallocator = "0.5.4"
117117
mimalloc = "0.1.52"
@@ -123,9 +123,9 @@ bs58 = "0.5.1"
123123
indexmap = "2.14"
124124
envsubst = "0.2.1"
125125
directories = "6.0.0"
126-
redis = "1.4.1"
126+
redis = "1.5.0"
127127
redis-test = "1.0.4"
128-
rustls = "0.23.42"
128+
rustls = "0.23.43"
129129
object_store = "0.14.1"
130130
parse-display = "0.11.0"
131131
toml = "1.1.3"

‎ceres/src/application/api_service/mono/cl_list.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ impl ClApplicationService {
2424
.bot_names_among(&names)
2525
.await?;
2626
ItemRes::apply_bot_flags(&mut items, &bot_names);
27+
28+
let links: Vec<String> = items.iter().map(|i| i.link.clone()).collect();
29+
match self
30+
.storage()
31+
.cl_service
32+
.cl_store()
33+
.latest_build_status_by_cl_links(&links)
34+
.await
35+
{
36+
Ok(statuses) => ItemRes::apply_build_statuses(&mut items, &statuses),
37+
Err(e) => {
38+
tracing::warn!("Failed to load CL build statuses for list: {e}");
39+
}
40+
}
41+
2742
Ok(CommonPage { items, total })
2843
}
2944

‎ceres/src/model/issue.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ pub struct ItemRes {
2828
pub labels: Vec<LabelItem>,
2929
pub assignees: Vec<String>,
3030
pub comment_num: usize,
31+
/// Aggregated Orion build status for this CL (latest task), if any.
32+
/// Issue list leaves this unset/`null`.
33+
#[serde(default, skip_serializing_if = "Option::is_none")]
34+
pub build_status: Option<String>,
3135
}
3236

3337
impl From<ItemDetails> for ItemRes {
@@ -47,6 +51,7 @@ impl From<ItemDetails> for ItemRes {
4751
labels: value.labels.into_iter().map(|m| m.into()).collect(),
4852
assignees: value.assignees,
4953
comment_num: value.comment_num,
54+
build_status: None,
5055
},
5156
ItemKind::Cl(model) => Self {
5257
id: model.id,
@@ -62,6 +67,7 @@ impl From<ItemDetails> for ItemRes {
6267
labels: value.labels.into_iter().map(|m| m.into()).collect(),
6368
assignees: value.assignees,
6469
comment_num: value.comment_num,
70+
build_status: None,
6571
},
6672
}
6773
}
@@ -73,6 +79,15 @@ impl ItemRes {
7379
item.author_is_bot = bot_names.contains(&item.author) || item.author == "system";
7480
}
7581
}
82+
83+
pub fn apply_build_statuses(
84+
items: &mut [Self],
85+
statuses: &std::collections::HashMap<String, String>,
86+
) {
87+
for item in items {
88+
item.build_status = statuses.get(&item.link).cloned();
89+
}
90+
}
7691
}
7792

7893
#[derive(Serialize, Deserialize, ToSchema)]

‎jupiter/src/storage/cl_storage.rs‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,74 @@ impl ClStorage {
447447
self.batch_save_model(save_models).await?;
448448
Ok(())
449449
}
450+
451+
/// For each CL link, resolve the latest Orion task and aggregate its
452+
/// `build_targets.latest_state` with Checks-compatible worst-wins priority:
453+
/// Failed > Interrupted > Building > Pending/Uninitialized > Completed.
454+
pub async fn latest_build_status_by_cl_links(
455+
&self,
456+
links: &[String],
457+
) -> Result<HashMap<String, String>, MegaError> {
458+
if links.is_empty() {
459+
return Ok(HashMap::new());
460+
}
461+
462+
let tasks = callisto::orion_tasks::Entity::find()
463+
.filter(callisto::orion_tasks::Column::Cl.is_in(links.to_vec()))
464+
.order_by_desc(callisto::orion_tasks::Column::CreatedAt)
465+
.all(self.get_connection())
466+
.await?;
467+
468+
// First row per CL wins (already ordered by created_at DESC).
469+
let mut latest_task_by_cl: HashMap<String, uuid::Uuid> = HashMap::new();
470+
for task in tasks {
471+
latest_task_by_cl.entry(task.cl).or_insert(task.id);
472+
}
473+
474+
if latest_task_by_cl.is_empty() {
475+
return Ok(HashMap::new());
476+
}
477+
478+
let task_ids: Vec<uuid::Uuid> = latest_task_by_cl.values().copied().collect();
479+
let targets = callisto::build_targets::Entity::find()
480+
.filter(callisto::build_targets::Column::TaskId.is_in(task_ids))
481+
.all(self.get_connection())
482+
.await?;
483+
484+
let mut states_by_task: HashMap<uuid::Uuid, Vec<String>> = HashMap::new();
485+
for target in targets {
486+
states_by_task
487+
.entry(target.task_id)
488+
.or_default()
489+
.push(target.latest_state);
490+
}
491+
492+
let mut result = HashMap::new();
493+
for (cl, task_id) in latest_task_by_cl {
494+
if let Some(states) = states_by_task.get(&task_id)
495+
&& let Some(status) = aggregate_target_states(states)
496+
{
497+
result.insert(cl, status);
498+
}
499+
}
500+
Ok(result)
501+
}
502+
}
503+
504+
/// Worst-wins aggregation matching Checks UI `getTaskStatus` priority.
505+
fn aggregate_target_states(states: &[String]) -> Option<String> {
506+
if states.is_empty() {
507+
return None;
508+
}
509+
let priority = |s: &str| -> u8 {
510+
match s {
511+
"Failed" => 0,
512+
"Interrupted" => 1,
513+
"Building" => 2,
514+
"Pending" | "Uninitialized" => 3,
515+
"Completed" => 4,
516+
_ => 3,
517+
}
518+
};
519+
states.iter().min_by_key(|s| priority(s.as_str())).cloned()
450520
}

‎mono/Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ────── Stage 0: Chef ──────
22
# Downloads and installs cargo-chef (prebuilt binary, no Rust compilation needed).
3-
FROM rust:1.96.0 AS chef
3+
FROM rust:1.97.1 AS chef
44

55
ARG CARGO_CHEF_VERSION=0.1.77
66

‎moon/apps/web/components/ClView/ClList.tsx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import React, { forwardRef, memo, ReactNode } from 'react'
22

33
import { PostApiIssueListData } from '@gitmono/types'
4-
import { Button, ChatBubbleIcon, Command, ConditionalWrap, LoadingSpinner, useCommand } from '@gitmono/ui'
4+
import { Button, ChatBubbleIcon, Command, ConditionalWrap, LoadingSpinner, Tooltip, useCommand } from '@gitmono/ui'
55

66
import { MemberHoverAvatarList } from '@/components/Issues/MemberHoverAvatarList'
77
import { SubjectCommand } from '@/components/Subject/SubjectCommand'
88
import { BreadcrumbTitlebarContainer } from '@/components/Titlebar/BreadcrumbTitlebar'
99
import { getFontColor } from '@/utils/getFontColor'
1010

11+
import { identifyStatus } from './components/Checks/cpns/identifyStatus'
12+
import { Status } from './components/Checks/cpns/store'
13+
1114
export function List<T>({
1215
lists,
1316
header,
@@ -196,9 +199,21 @@ export const ItemLabels = ({ item }: { item: ItemsType[number] }) => {
196199
}
197200

198201
export const ItemRightIcons = ({ item }: { item: ItemsType[number] }) => {
202+
const buildStatus = item.build_status
203+
const statusEnum =
204+
buildStatus && Object.values(Status).includes(buildStatus as Status) ? (buildStatus as Status) : null
205+
199206
return (
200207
// <div className='mr-10 flex w-fit items-center justify-between gap-10'>
201208
<div className='flex items-center gap-4'>
209+
{statusEnum ? (
210+
<Tooltip label={statusEnum}>
211+
<span className='flex items-center' aria-label={`Build ${statusEnum}`}>
212+
{identifyStatus(statusEnum)}
213+
</span>
214+
</Tooltip>
215+
) : null}
216+
202217
<div
203218
style={{
204219
visibility: `${item.comment_num === 0 ? 'hidden' : 'unset'}`

‎moon/apps/web/components/ClView/components/Checks/cpns/Task.tsx‎

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { LoadingSpinner } from '@gitmono/ui/Spinner'
88
import { usePostRetryBuild } from '@/hooks/SSE/usePostRetryBuild'
99

1010
import { TERMINAL_BUILD_STATUSES } from '../hooks/logUtils'
11+
import { identifyStatus } from './identifyStatus'
1112
import { BuildDTO, getLatestBuildId, isTaskQueued, Status, TaskInfoDTO } from './store'
1213

1314
/**
@@ -371,24 +372,4 @@ const TaskItem = memo(function TaskItem({
371372
)
372373
})
373374

374-
export const identifyStatus = (status: Status[keyof Status]) => {
375-
switch (status) {
376-
case Status.Completed:
377-
return <CheckIcon size={14} className='text-green-700 dark:text-green-400' />
378-
case Status.Failed:
379-
return <XIcon size={14} className='text-red-600 dark:text-red-400' />
380-
case Status.Interrupted:
381-
return <XIcon size={14} className='text-orange-600 dark:text-orange-400' />
382-
case Status.Building:
383-
return <LoadingSpinner />
384-
case Status.Pending:
385-
return <LoadingSpinner />
386-
case Status.Uninitialized:
387-
return <LoadingSpinner />
388-
case Status.NotFound:
389-
return <LoadingSpinner />
390-
391-
default:
392-
return <XIcon size={14} className='text-red-600 dark:text-red-400' />
393-
}
394-
}
375+
export { identifyStatus } from './identifyStatus'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { CheckIcon, XIcon } from '@primer/octicons-react'
2+
3+
import { LoadingSpinner } from '@gitmono/ui/Spinner'
4+
5+
import { Status } from './store'
6+
7+
export const identifyStatus = (status: Status[keyof Status]) => {
8+
switch (status) {
9+
case Status.Completed:
10+
return <CheckIcon size={14} className='text-green-700 dark:text-green-400' />
11+
case Status.Failed:
12+
return <XIcon size={14} className='text-red-600 dark:text-red-400' />
13+
case Status.Interrupted:
14+
return <XIcon size={14} className='text-orange-600 dark:text-orange-400' />
15+
case Status.Building:
16+
return <LoadingSpinner />
17+
case Status.Pending:
18+
return <LoadingSpinner />
19+
case Status.Uninitialized:
20+
return <LoadingSpinner />
21+
case Status.NotFound:
22+
return <LoadingSpinner />
23+
24+
default:
25+
return <XIcon size={14} className='text-red-600 dark:text-red-400' />
26+
}
27+
}

0 commit comments

Comments
 (0)