1#![allow(dead_code)]
8
9#[derive(Debug, Clone)]
11pub struct StealableTask {
12 pub id: u64,
14 pub priority: u32,
16 pub payload: String,
18}
19
20impl StealableTask {
21 #[must_use]
23 pub fn new(id: u64, priority: u32, payload: impl Into<String>) -> Self {
24 Self {
25 id,
26 priority,
27 payload: payload.into(),
28 }
29 }
30
31 #[must_use]
33 pub fn is_high_priority(&self) -> bool {
34 self.priority >= 10
35 }
36}
37
38#[derive(Debug)]
43pub struct WorkQueue {
44 pub deque: Vec<StealableTask>,
46 pub owner_id: u32,
48}
49
50impl WorkQueue {
51 #[must_use]
53 pub fn new(owner_id: u32) -> Self {
54 Self {
55 deque: Vec::new(),
56 owner_id,
57 }
58 }
59
60 pub fn push(&mut self, task: StealableTask) {
62 self.deque.push(task);
63 }
64
65 pub fn pop(&mut self) -> Option<StealableTask> {
69 self.deque.pop()
70 }
71
72 pub fn steal(&mut self) -> Option<StealableTask> {
76 if self.deque.is_empty() {
77 None
78 } else {
79 Some(self.deque.remove(0))
80 }
81 }
82
83 #[must_use]
85 pub fn len(&self) -> usize {
86 self.deque.len()
87 }
88
89 #[must_use]
91 pub fn is_empty(&self) -> bool {
92 self.deque.is_empty()
93 }
94
95 #[must_use]
97 pub fn high_priority_tasks(&self) -> Vec<&StealableTask> {
98 self.deque.iter().filter(|t| t.is_high_priority()).collect()
99 }
100}
101
102#[derive(Debug, Default)]
104pub struct WorkStealingScheduler {
105 pub queues: Vec<WorkQueue>,
107}
108
109impl WorkStealingScheduler {
110 #[must_use]
112 pub fn new() -> Self {
113 Self { queues: Vec::new() }
114 }
115
116 pub fn add_queue(&mut self, owner_id: u32) {
120 if !self.queues.iter().any(|q| q.owner_id == owner_id) {
121 self.queues.push(WorkQueue::new(owner_id));
122 }
123 }
124
125 pub fn submit_task(&mut self, owner_id: u32, task: StealableTask) -> bool {
129 if let Some(queue) = self.queues.iter_mut().find(|q| q.owner_id == owner_id) {
130 queue.push(task);
131 true
132 } else {
133 false
134 }
135 }
136
137 pub fn steal_from(&mut self, target_id: u32) -> Option<StealableTask> {
141 self.queues
142 .iter_mut()
143 .find(|q| q.owner_id == target_id)
144 .and_then(WorkQueue::steal)
145 }
146
147 pub fn pop_for(&mut self, owner_id: u32) -> Option<StealableTask> {
149 self.queues
150 .iter_mut()
151 .find(|q| q.owner_id == owner_id)
152 .and_then(WorkQueue::pop)
153 }
154
155 #[must_use]
157 pub fn total_pending(&self) -> usize {
158 self.queues.iter().map(WorkQueue::len).sum()
159 }
160
161 #[must_use]
163 pub fn queue_count(&self) -> usize {
164 self.queues.len()
165 }
166
167 #[must_use]
169 pub fn busiest_owner(&self) -> Option<u32> {
170 self.queues
171 .iter()
172 .max_by_key(|q| q.len())
173 .filter(|q| !q.is_empty())
174 .map(|q| q.owner_id)
175 }
176
177 #[must_use]
179 pub fn idlest_owner(&self) -> Option<u32> {
180 self.queues
181 .iter()
182 .min_by_key(|q| q.len())
183 .map(|q| q.owner_id)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 fn task(id: u64, priority: u32) -> StealableTask {
192 StealableTask::new(id, priority, format!("payload_{}", id))
193 }
194
195 #[test]
196 fn test_stealable_task_is_high_priority() {
197 assert!(task(1, 10).is_high_priority());
198 assert!(task(2, 15).is_high_priority());
199 assert!(!task(3, 9).is_high_priority());
200 assert!(!task(4, 0).is_high_priority());
201 }
202
203 #[test]
204 fn test_work_queue_push_pop_lifo() {
205 let mut q = WorkQueue::new(1);
206 q.push(task(1, 5));
207 q.push(task(2, 5));
208 q.push(task(3, 5));
209
210 assert_eq!(q.pop().expect("pop should return a value").id, 3);
212 assert_eq!(q.pop().expect("pop should return a value").id, 2);
213 assert_eq!(q.pop().expect("pop should return a value").id, 1);
214 assert!(q.pop().is_none());
215 }
216
217 #[test]
218 fn test_work_queue_steal_fifo() {
219 let mut q = WorkQueue::new(1);
220 q.push(task(1, 5));
221 q.push(task(2, 5));
222 q.push(task(3, 5));
223
224 assert_eq!(q.steal().expect("steal should return a task").id, 1);
226 assert_eq!(q.steal().expect("steal should return a task").id, 2);
227 assert_eq!(q.steal().expect("steal should return a task").id, 3);
228 assert!(q.steal().is_none());
229 }
230
231 #[test]
232 fn test_work_queue_len_and_is_empty() {
233 let mut q = WorkQueue::new(1);
234 assert!(q.is_empty());
235 assert_eq!(q.len(), 0);
236
237 q.push(task(1, 5));
238 q.push(task(2, 5));
239 assert_eq!(q.len(), 2);
240 assert!(!q.is_empty());
241 }
242
243 #[test]
244 fn test_work_queue_high_priority_tasks() {
245 let mut q = WorkQueue::new(1);
246 q.push(task(1, 5));
247 q.push(task(2, 10));
248 q.push(task(3, 15));
249 q.push(task(4, 3));
250
251 let hi = q.high_priority_tasks();
252 assert_eq!(hi.len(), 2);
253 }
254
255 #[test]
256 fn test_scheduler_add_queue() {
257 let mut sched = WorkStealingScheduler::new();
258 sched.add_queue(0);
259 sched.add_queue(1);
260 sched.add_queue(2);
261 assert_eq!(sched.queue_count(), 3);
262
263 sched.add_queue(1);
265 assert_eq!(sched.queue_count(), 3);
266 }
267
268 #[test]
269 fn test_scheduler_submit_task() {
270 let mut sched = WorkStealingScheduler::new();
271 sched.add_queue(0);
272
273 assert!(sched.submit_task(0, task(1, 5)));
274 assert_eq!(sched.total_pending(), 1);
275
276 assert!(!sched.submit_task(99, task(2, 5)));
278 assert_eq!(sched.total_pending(), 1);
279 }
280
281 #[test]
282 fn test_scheduler_steal_from() {
283 let mut sched = WorkStealingScheduler::new();
284 sched.add_queue(0);
285 sched.submit_task(0, task(1, 5));
286 sched.submit_task(0, task(2, 5));
287
288 let stolen = sched.steal_from(0).expect("steal_from should succeed");
290 assert_eq!(stolen.id, 1);
291 assert_eq!(sched.total_pending(), 1);
292 }
293
294 #[test]
295 fn test_scheduler_steal_from_empty() {
296 let mut sched = WorkStealingScheduler::new();
297 sched.add_queue(0);
298 assert!(sched.steal_from(0).is_none());
299 }
300
301 #[test]
302 fn test_scheduler_steal_from_missing_queue() {
303 let mut sched = WorkStealingScheduler::new();
304 assert!(sched.steal_from(42).is_none());
305 }
306
307 #[test]
308 fn test_scheduler_pop_for() {
309 let mut sched = WorkStealingScheduler::new();
310 sched.add_queue(0);
311 sched.submit_task(0, task(1, 5));
312 sched.submit_task(0, task(2, 5));
313
314 let t = sched.pop_for(0).expect("pop_for should return a task");
316 assert_eq!(t.id, 2);
317 }
318
319 #[test]
320 fn test_scheduler_total_pending() {
321 let mut sched = WorkStealingScheduler::new();
322 sched.add_queue(0);
323 sched.add_queue(1);
324 sched.submit_task(0, task(1, 5));
325 sched.submit_task(0, task(2, 5));
326 sched.submit_task(1, task(3, 5));
327
328 assert_eq!(sched.total_pending(), 3);
329 }
330
331 #[test]
332 fn test_scheduler_busiest_owner() {
333 let mut sched = WorkStealingScheduler::new();
334 sched.add_queue(0);
335 sched.add_queue(1);
336 sched.submit_task(0, task(1, 5));
337 sched.submit_task(0, task(2, 5));
338 sched.submit_task(1, task(3, 5));
339
340 assert_eq!(sched.busiest_owner(), Some(0));
341 }
342
343 #[test]
344 fn test_scheduler_idlest_owner() {
345 let mut sched = WorkStealingScheduler::new();
346 sched.add_queue(0);
347 sched.add_queue(1);
348 sched.submit_task(0, task(1, 5));
349 sched.submit_task(0, task(2, 5));
350
351 assert_eq!(sched.idlest_owner(), Some(1));
352 }
353}