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