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