hermit/scheduler/
task.rs

1#![allow(clippy::type_complexity)]
2
3#[cfg(not(feature = "common-os"))]
4use alloc::boxed::Box;
5use alloc::collections::{LinkedList, VecDeque};
6use alloc::rc::Rc;
7use alloc::sync::Arc;
8use core::cell::RefCell;
9use core::num::NonZeroU64;
10use core::{cmp, fmt};
11
12use ahash::RandomState;
13use crossbeam_utils::CachePadded;
14use hashbrown::HashMap;
15use hermit_sync::{OnceCell, RwSpinLock};
16use memory_addresses::VirtAddr;
17
18use crate::arch::core_local::*;
19use crate::arch::scheduler::TaskStacks;
20#[cfg(not(feature = "common-os"))]
21use crate::arch::scheduler::TaskTLS;
22use crate::errno::Errno;
23use crate::executor::poll_on;
24use crate::fd::stdio::*;
25use crate::fd::{FileDescriptor, ObjectInterface, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
26use crate::scheduler::CoreId;
27use crate::{arch, env};
28
29/// Returns the most significant bit.
30///
31/// # Examples
32///
33/// ```
34/// assert_eq!(msb(0), None);
35/// assert_eq!(msb(1), 0);
36/// assert_eq!(msb(u64::MAX), 63);
37/// ```
38#[inline]
39fn msb(n: u64) -> Option<u32> {
40	NonZeroU64::new(n).map(|n| u64::BITS - 1 - n.leading_zeros())
41}
42
43/// The status of the task - used for scheduling
44#[derive(Copy, Clone, Debug, Eq, PartialEq)]
45pub(crate) enum TaskStatus {
46	Invalid,
47	Ready,
48	Running,
49	Blocked,
50	Finished,
51	Idle,
52}
53
54/// Unique identifier for a task (i.e. `pid`).
55#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
56pub struct TaskId(i32);
57
58impl TaskId {
59	pub const fn into(self) -> i32 {
60		self.0
61	}
62
63	pub const fn from(x: i32) -> Self {
64		TaskId(x)
65	}
66}
67
68impl fmt::Display for TaskId {
69	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70		write!(f, "{}", self.0)
71	}
72}
73
74/// Priority of a task
75#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
76pub struct Priority(u8);
77
78impl Priority {
79	pub const fn into(self) -> u8 {
80		self.0
81	}
82
83	pub const fn from(x: u8) -> Self {
84		Priority(x)
85	}
86}
87
88impl fmt::Display for Priority {
89	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90		write!(f, "{}", self.0)
91	}
92}
93
94#[allow(dead_code)]
95pub const HIGH_PRIO: Priority = Priority::from(3);
96pub const NORMAL_PRIO: Priority = Priority::from(2);
97#[allow(dead_code)]
98pub const LOW_PRIO: Priority = Priority::from(1);
99pub const IDLE_PRIO: Priority = Priority::from(0);
100
101/// Maximum number of priorities
102pub const NO_PRIORITIES: usize = 31;
103
104#[derive(Copy, Clone, Debug)]
105pub(crate) struct TaskHandle {
106	id: TaskId,
107	priority: Priority,
108	#[cfg(feature = "smp")]
109	core_id: CoreId,
110}
111
112impl TaskHandle {
113	pub fn new(id: TaskId, priority: Priority, #[cfg(feature = "smp")] core_id: CoreId) -> Self {
114		Self {
115			id,
116			priority,
117			#[cfg(feature = "smp")]
118			core_id,
119		}
120	}
121
122	#[cfg(feature = "smp")]
123	pub fn get_core_id(&self) -> CoreId {
124		self.core_id
125	}
126
127	pub fn get_id(&self) -> TaskId {
128		self.id
129	}
130
131	pub fn get_priority(&self) -> Priority {
132		self.priority
133	}
134}
135
136impl Ord for TaskHandle {
137	fn cmp(&self, other: &Self) -> cmp::Ordering {
138		self.id.cmp(&other.id)
139	}
140}
141
142impl PartialOrd for TaskHandle {
143	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
144		Some(self.cmp(other))
145	}
146}
147
148impl PartialEq for TaskHandle {
149	fn eq(&self, other: &Self) -> bool {
150		self.id == other.id
151	}
152}
153
154impl Eq for TaskHandle {}
155
156/// Realize a priority queue for task handles
157#[derive(Default)]
158pub(crate) struct TaskHandlePriorityQueue {
159	queues: [Option<VecDeque<TaskHandle>>; NO_PRIORITIES],
160	prio_bitmap: CachePadded<u64>,
161}
162
163impl TaskHandlePriorityQueue {
164	/// Creates an empty priority queue for tasks
165	pub const fn new() -> Self {
166		Self {
167			queues: [const { None }; NO_PRIORITIES],
168			prio_bitmap: CachePadded::new(0),
169		}
170	}
171
172	/// Checks if the queue is empty.
173	pub fn is_empty(&self) -> bool {
174		self.prio_bitmap.into_inner() == 0
175	}
176
177	/// Checks if the given task is in the queue. Returns `true` if the task
178	/// was found.
179	pub fn contains(&self, task: TaskHandle) -> bool {
180		matches!(self.queues[task.priority.into() as usize]
181			.as_ref(), Some(queue) if queue.iter().any(|queued| queued.id == task.id))
182	}
183
184	/// Add a task handle by its priority to the queue
185	pub fn push(&mut self, task: TaskHandle) {
186		let i = task.priority.into() as usize;
187		//assert!(i < NO_PRIORITIES, "Priority {} is too high", i);
188
189		*self.prio_bitmap |= (1 << i) as u64;
190		if let Some(queue) = &mut self.queues[i] {
191			queue.push_back(task);
192		} else {
193			let mut queue = VecDeque::new();
194			queue.push_back(task);
195			self.queues[i] = Some(queue);
196		}
197	}
198
199	fn pop_from_queue(&mut self, queue_index: usize) -> Option<TaskHandle> {
200		if let Some(queue) = &mut self.queues[queue_index] {
201			let task = queue.pop_front();
202
203			if queue.is_empty() {
204				*self.prio_bitmap &= !(1 << queue_index as u64);
205			}
206
207			task
208		} else {
209			None
210		}
211	}
212
213	/// Pop the task handle with the highest priority from the queue
214	pub fn pop(&mut self) -> Option<TaskHandle> {
215		if let Some(i) = msb(self.prio_bitmap.into_inner()) {
216			return self.pop_from_queue(i as usize);
217		}
218
219		None
220	}
221
222	/// Remove a specific task handle from the priority queue. Returns `true` if
223	/// the handle was in the queue.
224	pub fn remove(&mut self, task: TaskHandle) -> bool {
225		let queue_index = task.priority.into() as usize;
226		//assert!(queue_index < NO_PRIORITIES, "Priority {} is too high", queue_index);
227
228		let mut success = false;
229		if let Some(queue) = &mut self.queues[queue_index] {
230			let mut i = 0;
231			while i != queue.len() {
232				if queue[i].id == task.id {
233					queue.remove(i);
234					success = true;
235				} else {
236					i += 1;
237				}
238			}
239
240			if queue.is_empty() {
241				*self.prio_bitmap &= !(1 << queue_index as u64);
242			}
243		}
244
245		success
246	}
247}
248
249/// Realize a priority queue for tasks
250pub(crate) struct PriorityTaskQueue {
251	queues: [LinkedList<Rc<RefCell<Task>>>; NO_PRIORITIES],
252	prio_bitmap: u64,
253}
254
255impl PriorityTaskQueue {
256	/// Creates an empty priority queue for tasks
257	pub const fn new() -> PriorityTaskQueue {
258		const EMPTY_LIST: LinkedList<Rc<RefCell<Task>>> = LinkedList::new();
259		PriorityTaskQueue {
260			queues: [EMPTY_LIST; NO_PRIORITIES],
261			prio_bitmap: 0,
262		}
263	}
264
265	/// Add a task by its priority to the queue
266	pub fn push(&mut self, task: Rc<RefCell<Task>>) {
267		let i = task.borrow().prio.into() as usize;
268		//assert!(i < NO_PRIORITIES, "Priority {} is too high", i);
269
270		self.prio_bitmap |= (1 << i) as u64;
271		let queue = &mut self.queues[i];
272		queue.push_back(task);
273	}
274
275	fn pop_from_queue(&mut self, queue_index: usize) -> Option<Rc<RefCell<Task>>> {
276		let task = self.queues[queue_index].pop_front();
277		if self.queues[queue_index].is_empty() {
278			self.prio_bitmap &= !(1 << queue_index as u64);
279		}
280
281		task
282	}
283
284	/// Remove the task at index from the queue and return that task,
285	/// or None if the index is out of range or the list is empty.
286	fn remove_from_queue(
287		&mut self,
288		task_index: usize,
289		queue_index: usize,
290	) -> Option<Rc<RefCell<Task>>> {
291		//assert!(prio < NO_PRIORITIES, "Priority {} is too high", prio);
292
293		let queue = &mut self.queues[queue_index];
294		if task_index <= queue.len() {
295			// Calling remove is unstable: https://github.com/rust-lang/rust/issues/69210
296			let mut split_list = queue.split_off(task_index);
297			let element = split_list.pop_front();
298			queue.append(&mut split_list);
299			if queue.is_empty() {
300				self.prio_bitmap &= !(1 << queue_index as u64);
301			}
302			element
303		} else {
304			None
305		}
306	}
307
308	/// Returns true if the queue is empty.
309	pub fn is_empty(&self) -> bool {
310		self.prio_bitmap == 0
311	}
312
313	/// Returns reference to prio_bitmap
314	#[allow(dead_code)]
315	#[inline]
316	pub fn get_priority_bitmap(&self) -> &u64 {
317		&self.prio_bitmap
318	}
319
320	/// Pop the task with the highest priority from the queue
321	pub fn pop(&mut self) -> Option<Rc<RefCell<Task>>> {
322		if let Some(i) = msb(self.prio_bitmap) {
323			return self.pop_from_queue(i as usize);
324		}
325
326		None
327	}
328
329	/// Pop the next task, which has a higher or the same priority as `prio`
330	pub fn pop_with_prio(&mut self, prio: Priority) -> Option<Rc<RefCell<Task>>> {
331		if let Some(i) = msb(self.prio_bitmap)
332			&& i >= u32::from(prio.into())
333		{
334			return self.pop_from_queue(i as usize);
335		}
336
337		None
338	}
339
340	/// Returns the highest priority of all available task
341	#[cfg(all(any(target_arch = "x86_64", target_arch = "riscv64"), feature = "smp"))]
342	pub fn get_highest_priority(&self) -> Priority {
343		if let Some(i) = msb(self.prio_bitmap) {
344			Priority::from(i.try_into().unwrap())
345		} else {
346			IDLE_PRIO
347		}
348	}
349
350	/// Change priority of specific task
351	pub fn set_priority(&mut self, handle: TaskHandle, prio: Priority) -> Result<(), ()> {
352		let old_priority = handle.get_priority().into() as usize;
353		if let Some(index) = self.queues[old_priority]
354			.iter()
355			.position(|current_task| current_task.borrow().id == handle.id)
356		{
357			let Some(task) = self.remove_from_queue(index, old_priority) else {
358				return Err(());
359			};
360			task.borrow_mut().prio = prio;
361			self.push(task);
362			return Ok(());
363		}
364
365		Err(())
366	}
367}
368
369/// A task control block, which identifies either a process or a thread
370#[cfg_attr(any(target_arch = "x86_64", target_arch = "aarch64"), repr(align(128)))]
371#[cfg_attr(
372	not(any(target_arch = "x86_64", target_arch = "aarch64")),
373	repr(align(64))
374)]
375pub(crate) struct Task {
376	/// The ID of this context
377	pub id: TaskId,
378	/// Status of a task, e.g. if the task is ready or blocked
379	pub status: TaskStatus,
380	/// Task priority,
381	pub prio: Priority,
382	/// Last stack pointer before a context switch to another task
383	pub last_stack_pointer: VirtAddr,
384	/// Last stack pointer on the user stack before jumping to kernel space
385	pub user_stack_pointer: VirtAddr,
386	/// Last FPU state before a context switch to another task using the FPU
387	pub last_fpu_state: arch::processor::FPUState,
388	/// ID of the core this task is running on
389	pub core_id: CoreId,
390	/// Stack of the task
391	pub stacks: TaskStacks,
392	/// Mapping between file descriptor and the referenced IO interface
393	pub object_map: Arc<
394		RwSpinLock<
395			HashMap<FileDescriptor, Arc<async_lock::RwLock<dyn ObjectInterface>>, RandomState>,
396		>,
397	>,
398	/// Task Thread-Local-Storage (TLS)
399	#[cfg(not(feature = "common-os"))]
400	pub tls: Option<Box<TaskTLS>>,
401	// Physical address of the 1st level page table
402	#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
403	pub root_page_table: usize,
404}
405
406pub(crate) trait TaskFrame {
407	/// Create the initial stack frame for a new task
408	fn create_stack_frame(&mut self, func: unsafe extern "C" fn(usize), arg: usize);
409}
410
411impl Task {
412	pub fn new(
413		tid: TaskId,
414		core_id: CoreId,
415		task_status: TaskStatus,
416		task_prio: Priority,
417		stacks: TaskStacks,
418		object_map: Arc<
419			RwSpinLock<
420				HashMap<FileDescriptor, Arc<async_lock::RwLock<dyn ObjectInterface>>, RandomState>,
421			>,
422		>,
423	) -> Task {
424		debug!("Creating new task {tid} on core {core_id}");
425
426		Task {
427			id: tid,
428			status: task_status,
429			prio: task_prio,
430			last_stack_pointer: VirtAddr::zero(),
431			user_stack_pointer: VirtAddr::zero(),
432			last_fpu_state: arch::processor::FPUState::new(),
433			core_id,
434			stacks,
435			object_map,
436			#[cfg(not(feature = "common-os"))]
437			tls: None,
438			#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
439			root_page_table: arch::create_new_root_page_table(),
440		}
441	}
442
443	pub fn new_idle(tid: TaskId, core_id: CoreId) -> Task {
444		debug!("Creating idle task {tid}");
445
446		/// All cores use the same mapping between file descriptor and the referenced object
447		static OBJECT_MAP: OnceCell<
448			Arc<
449				RwSpinLock<
450					HashMap<
451						FileDescriptor,
452						Arc<async_lock::RwLock<dyn ObjectInterface>>,
453						RandomState,
454					>,
455				>,
456			>,
457		> = OnceCell::new();
458
459		if core_id == 0 {
460			OBJECT_MAP
461				.set(Arc::new(RwSpinLock::new(HashMap::<
462					FileDescriptor,
463					Arc<async_lock::RwLock<dyn ObjectInterface>>,
464					RandomState,
465				>::with_hasher(
466					RandomState::with_seeds(0, 0, 0, 0),
467				))))
468				.unwrap();
469			let objmap = OBJECT_MAP.get().unwrap().clone();
470			let _ = poll_on(async {
471				let mut guard = objmap.write();
472				if env::is_uhyve() {
473					guard
474						.try_insert(
475							STDIN_FILENO,
476							Arc::new(async_lock::RwLock::new(UhyveStdin::new())),
477						)
478						.map_err(|_| Errno::Io)?;
479					guard
480						.try_insert(
481							STDOUT_FILENO,
482							Arc::new(async_lock::RwLock::new(UhyveStdout::new())),
483						)
484						.map_err(|_| Errno::Io)?;
485					guard
486						.try_insert(
487							STDERR_FILENO,
488							Arc::new(async_lock::RwLock::new(UhyveStderr::new())),
489						)
490						.map_err(|_| Errno::Io)?;
491				} else {
492					guard
493						.try_insert(
494							STDIN_FILENO,
495							Arc::new(async_lock::RwLock::new(GenericStdin::new())),
496						)
497						.map_err(|_| Errno::Io)?;
498					guard
499						.try_insert(
500							STDOUT_FILENO,
501							Arc::new(async_lock::RwLock::new(GenericStdout::new())),
502						)
503						.map_err(|_| Errno::Io)?;
504					guard
505						.try_insert(
506							STDERR_FILENO,
507							Arc::new(async_lock::RwLock::new(GenericStderr::new())),
508						)
509						.map_err(|_| Errno::Io)?;
510				}
511
512				Ok(())
513			});
514		}
515
516		Task {
517			id: tid,
518			status: TaskStatus::Idle,
519			prio: IDLE_PRIO,
520			last_stack_pointer: VirtAddr::zero(),
521			user_stack_pointer: VirtAddr::zero(),
522			last_fpu_state: arch::processor::FPUState::new(),
523			core_id,
524			stacks: TaskStacks::from_boot_stacks(),
525			object_map: OBJECT_MAP.get().unwrap().clone(),
526			#[cfg(not(feature = "common-os"))]
527			tls: None,
528			#[cfg(all(target_arch = "x86_64", feature = "common-os"))]
529			root_page_table: *crate::scheduler::BOOT_ROOT_PAGE_TABLE.get().unwrap(),
530		}
531	}
532}
533
534/*impl Drop for Task {
535	fn drop(&mut self) {
536		debug!("Drop task {}", self.id);
537	}
538}*/
539
540struct BlockedTask {
541	task: Rc<RefCell<Task>>,
542	wakeup_time: Option<u64>,
543}
544
545impl BlockedTask {
546	pub fn new(task: Rc<RefCell<Task>>, wakeup_time: Option<u64>) -> Self {
547		Self { task, wakeup_time }
548	}
549}
550
551pub(crate) struct BlockedTaskQueue {
552	list: LinkedList<BlockedTask>,
553	#[cfg(feature = "net")]
554	network_wakeup_time: Option<u64>,
555}
556
557impl BlockedTaskQueue {
558	pub const fn new() -> Self {
559		Self {
560			list: LinkedList::new(),
561			#[cfg(feature = "net")]
562			network_wakeup_time: None,
563		}
564	}
565
566	fn mark_ready(task: &RefCell<Task>) {
567		let mut borrowed = task.borrow_mut();
568		debug!(
569			"Waking up task {} on core {}",
570			borrowed.id, borrowed.core_id
571		);
572
573		assert!(
574			borrowed.core_id == core_id(),
575			"Try to wake up task {} on the wrong core {} != {}",
576			borrowed.id,
577			borrowed.core_id,
578			core_id()
579		);
580
581		assert!(
582			borrowed.status == TaskStatus::Blocked,
583			"Trying to wake up task {} which is not blocked",
584			borrowed.id
585		);
586		borrowed.status = TaskStatus::Ready;
587	}
588
589	#[cfg(feature = "net")]
590	pub fn add_network_timer(&mut self, wakeup_time: Option<u64>) {
591		self.network_wakeup_time = wakeup_time;
592
593		let next = self.list.front().and_then(|t| t.wakeup_time);
594
595		let time = match (wakeup_time, next) {
596			(Some(a), Some(b)) => Some(a.min(b)),
597			(a, b) => a.or(b),
598		};
599
600		arch::set_oneshot_timer(time);
601	}
602
603	/// Blocks the given task for `wakeup_time` ticks, or indefinitely if None is given.
604	pub fn add(&mut self, task: Rc<RefCell<Task>>, wakeup_time: Option<u64>) {
605		{
606			// Set the task status to Blocked.
607			let mut borrowed = task.borrow_mut();
608			debug!("Blocking task {}", borrowed.id);
609
610			assert_eq!(
611				borrowed.status,
612				TaskStatus::Running,
613				"Trying to block task {} which is not running",
614				borrowed.id
615			);
616			borrowed.status = TaskStatus::Blocked;
617		}
618
619		let new_node = BlockedTask::new(task, wakeup_time);
620
621		// Shall the task automatically be woken up after a certain time?
622		if let Some(wt) = wakeup_time {
623			let mut cursor = self.list.cursor_front_mut();
624			let set_oneshot_timer = || {
625				#[cfg(not(feature = "net"))]
626				arch::set_oneshot_timer(wakeup_time);
627				#[cfg(feature = "net")]
628				match self.network_wakeup_time {
629					Some(time) => {
630						if time > wt {
631							arch::set_oneshot_timer(wakeup_time);
632						} else {
633							arch::set_oneshot_timer(self.network_wakeup_time);
634						}
635					}
636					_ => arch::set_oneshot_timer(wakeup_time),
637				}
638			};
639
640			while let Some(node) = cursor.current() {
641				let node_wakeup_time = node.wakeup_time;
642				if node_wakeup_time.is_none() || wt < node_wakeup_time.unwrap() {
643					cursor.insert_before(new_node);
644
645					set_oneshot_timer();
646					return;
647				}
648
649				cursor.move_next();
650			}
651
652			set_oneshot_timer();
653		}
654
655		self.list.push_back(new_node);
656	}
657
658	/// Manually wake up a blocked task.
659	pub fn custom_wakeup(&mut self, task: TaskHandle) -> Rc<RefCell<Task>> {
660		let mut first_task = true;
661		let mut cursor = self.list.cursor_front_mut();
662
663		#[cfg(feature = "net")]
664		if let Some(wakeup_time) = self.network_wakeup_time
665			&& wakeup_time <= arch::processor::get_timer_ticks()
666		{
667			self.network_wakeup_time = None;
668		}
669
670		// Loop through all blocked tasks to find it.
671		while let Some(node) = cursor.current() {
672			if node.task.borrow().id == task.get_id() {
673				// Remove it from the list of blocked tasks.
674				let task_ref = node.task.clone();
675				cursor.remove_current();
676
677				// If this is the first task, adjust the One-Shot Timer to fire at the
678				// next task's wakeup time (if any).
679				#[cfg(feature = "net")]
680				if first_task {
681					arch::set_oneshot_timer(cursor.current().map_or_else(
682						|| self.network_wakeup_time,
683						|node| match node.wakeup_time {
684							Some(wt) => {
685								if let Some(timer) = self.network_wakeup_time {
686									if wt < timer { Some(wt) } else { Some(timer) }
687								} else {
688									Some(wt)
689								}
690							}
691							None => self.network_wakeup_time,
692						},
693					));
694				}
695				#[cfg(not(feature = "net"))]
696				if first_task {
697					arch::set_oneshot_timer(
698						cursor
699							.current()
700							.map_or_else(|| None, |node| node.wakeup_time),
701					);
702				}
703
704				// Wake it up.
705				Self::mark_ready(&task_ref);
706
707				return task_ref;
708			}
709
710			first_task = false;
711			cursor.move_next();
712		}
713
714		unreachable!();
715	}
716
717	/// Wakes up all tasks whose wakeup time has elapsed.
718	///
719	/// Should be called by the One-Shot Timer interrupt handler when the wakeup time for
720	/// at least one task has elapsed.
721	pub fn handle_waiting_tasks(&mut self, ready_queue: &mut PriorityTaskQueue) {
722		// Get the current time.
723		let time = arch::processor::get_timer_ticks();
724
725		#[cfg(feature = "net")]
726		if let Some(mut guard) = crate::executor::network::NIC.try_lock()
727			&& let crate::executor::network::NetworkState::Initialized(nic) = &mut *guard
728		{
729			let now = crate::executor::network::now();
730			nic.poll_common(now);
731			self.network_wakeup_time = nic.poll_delay(now).map(|d| d.total_micros() + time);
732		}
733
734		// Get the wakeup time of this task and check if we have reached the first task
735		// that hasn't elapsed yet or waits indefinitely.
736		// This iterator has to be consumed to actually remove the elements.
737		let newly_ready_tasks = self.list.extract_if(|blocked_task| {
738			blocked_task
739				.wakeup_time
740				.is_some_and(|wakeup_time| wakeup_time < time)
741		});
742
743		for task in newly_ready_tasks {
744			Self::mark_ready(&task.task);
745			ready_queue.push(task.task);
746		}
747
748		let new_task_wakeup_time = self.list.front().and_then(|task| task.wakeup_time);
749		cfg_if::cfg_if! {
750			if 	#[cfg(feature = "net")] {
751				let network_wakeup_time = self.network_wakeup_time;
752			} else {
753				let network_wakeup_time = None;
754			}
755		};
756		let timer_wakeup_time = match (new_task_wakeup_time, network_wakeup_time) {
757			(None, None) => None,
758			(None, Some(network_wt)) => Some(network_wt),
759			(Some(task_wt), None) => Some(task_wt),
760			(Some(task_wt), Some(network_wt)) => Some(u64::min(task_wt, network_wt)),
761		};
762
763		arch::set_oneshot_timer(timer_wakeup_time);
764	}
765}