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 caller: Some(crate::identity::TaskCaller::Operator),
130 reference_task_ids: Vec::new(),
131 part_kinds: Vec::new(),
132 images: Vec::new(),
133 }
134 }
135
136 fn origin_for(lane: TaskLane) -> TaskOrigin {
139 match lane {
140 TaskLane::User => TaskOrigin::User,
141 TaskLane::Peer => TaskOrigin::Peer,
142 TaskLane::Bg => TaskOrigin::Event,
143 }
144 }
145
146 fn drain(queue: &mut LaneQueue) -> Vec<(TaskLane, String)> {
147 let mut drained = Vec::new();
148 while let Some((lane, task)) = queue.next(None) {
149 drained.push((lane, task.task_id));
150 }
151 drained
152 }
153
154 #[test]
155 fn every_origin_names_a_lane() {
156 for (origin, expected) in [
157 (TaskOrigin::User, TaskLane::User),
158 (TaskOrigin::Peer, TaskLane::Peer),
159 (TaskOrigin::Schedule, TaskLane::Bg),
160 (TaskOrigin::Event, TaskLane::Bg),
161 (TaskOrigin::Completion, TaskLane::Bg),
162 (TaskOrigin::System, TaskLane::Bg),
163 ] {
164 assert_eq!(
165 TaskLane::for_origin(origin),
166 expected,
167 "{origin:?} belongs in {expected:?}"
168 );
169 }
170 }
171
172 #[test]
173 fn lane_order_is_precedence() {
174 assert!(TaskLane::User > TaskLane::Peer);
175 assert!(TaskLane::Peer > TaskLane::Bg);
176 for (lane, spelling) in [
177 (TaskLane::User, "user"),
178 (TaskLane::Peer, "peer"),
179 (TaskLane::Bg, "bg"),
180 ] {
181 assert_eq!(lane.as_str(), spelling);
182 }
183 }
184
185 #[test]
188 fn every_lane_drains_before_the_one_below_it() {
189 let mut queue = LaneQueue::new();
190 for (id, origin) in [
191 ("bg-1", TaskOrigin::Schedule),
192 ("user-1", TaskOrigin::User),
193 ("bg-2", TaskOrigin::Event),
194 ("peer-1", TaskOrigin::Peer),
195 ("user-2", TaskOrigin::User),
196 ("bg-3", TaskOrigin::Completion),
197 ("peer-2", TaskOrigin::Peer),
198 ] {
199 queue.push(task(id, origin));
200 }
201
202 assert_eq!(
203 drain(&mut queue),
204 vec![
205 (TaskLane::User, "user-1".to_string()),
206 (TaskLane::User, "user-2".to_string()),
207 (TaskLane::Peer, "peer-1".to_string()),
208 (TaskLane::Peer, "peer-2".to_string()),
209 (TaskLane::Bg, "bg-1".to_string()),
210 (TaskLane::Bg, "bg-2".to_string()),
211 (TaskLane::Bg, "bg-3".to_string()),
212 ]
213 );
214 assert!(
215 queue.next(None).is_none(),
216 "the queue is empty once drained"
217 );
218 }
219
220 #[test]
222 fn a_user_task_behind_six_completions_runs_first() {
223 let mut queue = LaneQueue::new();
224 for i in 1..=6 {
225 queue.push(task(&format!("done-{i}"), TaskOrigin::Completion));
226 }
227 queue.push(task("asked", TaskOrigin::User));
228
229 let drained = drain(&mut queue);
230 assert_eq!(drained[0], (TaskLane::User, "asked".to_string()));
231 assert_eq!(
232 drained[1..],
233 (1..=6)
234 .map(|i| (TaskLane::Bg, format!("done-{i}")))
235 .collect::<Vec<_>>()
236 );
237 }
238
239 #[test]
240 fn tasks_of_one_origin_drain_in_push_order() {
241 let mut queue = LaneQueue::new();
242 for id in ["first", "second", "third"] {
243 queue.push(task(id, TaskOrigin::Peer));
244 }
245 assert_eq!(
246 drain(&mut queue),
247 vec![
248 (TaskLane::Peer, "first".to_string()),
249 (TaskLane::Peer, "second".to_string()),
250 (TaskLane::Peer, "third".to_string()),
251 ]
252 );
253 }
254
255 #[test]
258 fn nothing_is_yielded_while_a_task_is_active() {
259 for active in ALL_LANES {
260 for queued in ALL_LANES {
261 let mut queue = LaneQueue::new();
262 queue.push(task("waiting", origin_for(queued)));
263 assert!(
264 queue.next(Some(active)).is_none(),
265 "a {queued:?} task must not preempt an active {active:?} task"
266 );
267 assert_eq!(
268 queue.next(None).map(|(lane, task)| (lane, task.task_id)),
269 Some((queued, "waiting".to_string())),
270 "the refused {queued:?} task must still be queued afterwards"
271 );
272 }
273 }
274 }
275}