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