1use std::collections::VecDeque;
12
13use crate::{a2a::IncomingTask, origin::TaskOrigin};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub enum TaskLane {
18 Bg,
21 Peer,
23 User,
25}
26
27impl TaskLane {
28 pub fn for_origin(origin: TaskOrigin) -> Self {
33 match origin {
34 TaskOrigin::User => Self::User,
35 TaskOrigin::Peer => Self::Peer,
36 TaskOrigin::Schedule | TaskOrigin::Event | TaskOrigin::Completion => Self::Bg,
39 TaskOrigin::System => Self::Bg,
40 }
41 }
42
43 pub fn as_str(&self) -> &'static str {
45 match self {
46 Self::User => "user",
47 Self::Peer => "peer",
48 Self::Bg => "bg",
49 }
50 }
51}
52
53pub(crate) struct LaneQueue {
58 user: VecDeque<IncomingTask>,
59 peer: VecDeque<IncomingTask>,
60 bg: VecDeque<IncomingTask>,
61}
62
63impl LaneQueue {
64 pub(crate) fn new() -> Self {
65 Self {
66 user: VecDeque::new(),
67 peer: VecDeque::new(),
68 bg: VecDeque::new(),
69 }
70 }
71
72 pub(crate) fn push(&mut self, task: IncomingTask) {
76 let lane = TaskLane::for_origin(task.provenance.origin());
77 self.lane_mut(lane).push_back(task);
78 }
79
80 pub(crate) fn next(&mut self, active: Option<TaskLane>) -> Option<(TaskLane, IncomingTask)> {
91 if active.is_some() {
92 return None;
93 }
94 for lane in [TaskLane::User, TaskLane::Peer, TaskLane::Bg] {
95 if let Some(task) = self.lane_mut(lane).pop_front() {
96 return Some((lane, task));
97 }
98 }
99 None
100 }
101
102 fn lane_mut(&mut self, lane: TaskLane) -> &mut VecDeque<IncomingTask> {
103 match lane {
104 TaskLane::User => &mut self.user,
105 TaskLane::Peer => &mut self.peer,
106 TaskLane::Bg => &mut self.bg,
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::origin::TaskProvenance;
115
116 const ALL_LANES: [TaskLane; 3] = [TaskLane::User, TaskLane::Peer, TaskLane::Bg];
117
118 fn task(id: &str, origin: TaskOrigin) -> IncomingTask {
119 IncomingTask {
120 task_id: id.to_string(),
121 context_id: "ctx_001".to_string(),
122 message_id: format!("msg_{id}"),
123 message_text: id.to_string(),
124 traceparent: None,
125 provenance: TaskProvenance::derive(origin, None),
126 source: crate::a2a::SOURCE_A2A,
127 forget_session: false,
128 caller_member: None,
129 member_request_id: None,
130 caller: Some(crate::identity::TaskCaller::Operator),
131 reference_task_ids: Vec::new(),
132 part_kinds: Vec::new(),
133 images: Vec::new(),
134 }
135 }
136
137 fn origin_for(lane: TaskLane) -> TaskOrigin {
140 match lane {
141 TaskLane::User => TaskOrigin::User,
142 TaskLane::Peer => TaskOrigin::Peer,
143 TaskLane::Bg => TaskOrigin::Event,
144 }
145 }
146
147 fn drain(queue: &mut LaneQueue) -> Vec<(TaskLane, String)> {
148 let mut drained = Vec::new();
149 while let Some((lane, task)) = queue.next(None) {
150 drained.push((lane, task.task_id));
151 }
152 drained
153 }
154
155 #[test]
156 fn every_origin_names_a_lane() {
157 for (origin, expected) in [
158 (TaskOrigin::User, TaskLane::User),
159 (TaskOrigin::Peer, TaskLane::Peer),
160 (TaskOrigin::Schedule, TaskLane::Bg),
161 (TaskOrigin::Event, TaskLane::Bg),
162 (TaskOrigin::Completion, TaskLane::Bg),
163 (TaskOrigin::System, TaskLane::Bg),
164 ] {
165 assert_eq!(
166 TaskLane::for_origin(origin),
167 expected,
168 "{origin:?} belongs in {expected:?}"
169 );
170 }
171 }
172
173 #[test]
174 fn lane_order_is_precedence() {
175 assert!(TaskLane::User > TaskLane::Peer);
176 assert!(TaskLane::Peer > TaskLane::Bg);
177 for (lane, spelling) in [
178 (TaskLane::User, "user"),
179 (TaskLane::Peer, "peer"),
180 (TaskLane::Bg, "bg"),
181 ] {
182 assert_eq!(lane.as_str(), spelling);
183 }
184 }
185
186 #[test]
189 fn every_lane_drains_before_the_one_below_it() {
190 let mut queue = LaneQueue::new();
191 for (id, origin) in [
192 ("bg-1", TaskOrigin::Schedule),
193 ("user-1", TaskOrigin::User),
194 ("bg-2", TaskOrigin::Event),
195 ("peer-1", TaskOrigin::Peer),
196 ("user-2", TaskOrigin::User),
197 ("bg-3", TaskOrigin::Completion),
198 ("peer-2", TaskOrigin::Peer),
199 ] {
200 queue.push(task(id, origin));
201 }
202
203 assert_eq!(
204 drain(&mut queue),
205 vec![
206 (TaskLane::User, "user-1".to_string()),
207 (TaskLane::User, "user-2".to_string()),
208 (TaskLane::Peer, "peer-1".to_string()),
209 (TaskLane::Peer, "peer-2".to_string()),
210 (TaskLane::Bg, "bg-1".to_string()),
211 (TaskLane::Bg, "bg-2".to_string()),
212 (TaskLane::Bg, "bg-3".to_string()),
213 ]
214 );
215 assert!(
216 queue.next(None).is_none(),
217 "the queue is empty once drained"
218 );
219 }
220
221 #[test]
223 fn a_user_task_behind_six_completions_runs_first() {
224 let mut queue = LaneQueue::new();
225 for i in 1..=6 {
226 queue.push(task(&format!("done-{i}"), TaskOrigin::Completion));
227 }
228 queue.push(task("asked", TaskOrigin::User));
229
230 let drained = drain(&mut queue);
231 assert_eq!(drained[0], (TaskLane::User, "asked".to_string()));
232 assert_eq!(
233 drained[1..],
234 (1..=6)
235 .map(|i| (TaskLane::Bg, format!("done-{i}")))
236 .collect::<Vec<_>>()
237 );
238 }
239
240 #[test]
241 fn tasks_of_one_origin_drain_in_push_order() {
242 let mut queue = LaneQueue::new();
243 for id in ["first", "second", "third"] {
244 queue.push(task(id, TaskOrigin::Peer));
245 }
246 assert_eq!(
247 drain(&mut queue),
248 vec![
249 (TaskLane::Peer, "first".to_string()),
250 (TaskLane::Peer, "second".to_string()),
251 (TaskLane::Peer, "third".to_string()),
252 ]
253 );
254 }
255
256 #[test]
259 fn nothing_is_yielded_while_a_task_is_active() {
260 for active in ALL_LANES {
261 for queued in ALL_LANES {
262 let mut queue = LaneQueue::new();
263 queue.push(task("waiting", origin_for(queued)));
264 assert!(
265 queue.next(Some(active)).is_none(),
266 "a {queued:?} task must not preempt an active {active:?} task"
267 );
268 assert_eq!(
269 queue.next(None).map(|(lane, task)| (lane, task.task_id)),
270 Some((queued, "waiting".to_string())),
271 "the refused {queued:?} task must still be queued afterwards"
272 );
273 }
274 }
275 }
276}