2024-01-03 03:22:36 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
2024-06-03 18:34:33 +00:00
|
|
|
#![allow(dead_code)]
|
|
|
|
|
|
2024-06-08 08:17:00 +00:00
|
|
|
use core::{
|
|
|
|
|
sync::atomic::{AtomicBool, Ordering},
|
|
|
|
|
time::Duration,
|
|
|
|
|
};
|
2023-10-08 16:02:24 +00:00
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
use ostd::{
|
|
|
|
|
cpu::{CpuId, CpuSet},
|
|
|
|
|
sync::WaitQueue,
|
|
|
|
|
task::Task,
|
|
|
|
|
};
|
2024-02-25 14:09:24 +00:00
|
|
|
|
2023-10-08 16:02:24 +00:00
|
|
|
use super::{simple_scheduler::SimpleScheduler, worker::Worker, WorkItem, WorkPriority, WorkQueue};
|
2024-02-25 14:09:24 +00:00
|
|
|
use crate::{
|
|
|
|
|
prelude::*,
|
2024-09-14 07:32:21 +00:00
|
|
|
sched::priority::{Priority, PriorityRange},
|
2024-09-13 03:13:07 +00:00
|
|
|
thread::kernel_thread::{create_new_kernel_task, ThreadOptions},
|
2024-02-25 14:09:24 +00:00
|
|
|
Thread,
|
|
|
|
|
};
|
2023-10-08 16:02:24 +00:00
|
|
|
|
|
|
|
|
/// A pool of workers.
|
|
|
|
|
///
|
|
|
|
|
/// The `WorkerPool` maintains workers created from different CPUs, while clustering workers
|
|
|
|
|
/// from the same CPU into a `LocalWorkerPool` for better management.
|
|
|
|
|
pub struct WorkerPool {
|
|
|
|
|
local_pools: Vec<Arc<LocalWorkerPool>>,
|
|
|
|
|
/// Monitor invokes `schedule()` in WorkerScheduler to determine whether there is a need for
|
|
|
|
|
/// adding or removing workers.
|
|
|
|
|
monitor: Arc<Monitor>,
|
|
|
|
|
priority: WorkPriority,
|
|
|
|
|
cpu_set: CpuSet,
|
|
|
|
|
scheduler: Arc<dyn WorkerScheduler>,
|
|
|
|
|
work_queues: SpinLock<Vec<Arc<WorkQueue>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A set of workers for a specific CPU.
|
|
|
|
|
pub struct LocalWorkerPool {
|
2024-09-23 03:51:53 +00:00
|
|
|
cpu_id: CpuId,
|
2023-10-08 16:02:24 +00:00
|
|
|
idle_wait_queue: WaitQueue,
|
|
|
|
|
parent: Weak<WorkerPool>,
|
|
|
|
|
/// A liveness check for LocalWorkerPool. The monitor periodically clears heartbeat,
|
|
|
|
|
/// and when a worker completes an item, it will be set to indicate that there is still
|
|
|
|
|
/// an active worker. If there is no heartbeats and there are still pending work items,
|
|
|
|
|
/// it suggests that more workers are needed.
|
|
|
|
|
heartbeat: AtomicBool,
|
|
|
|
|
workers: SpinLock<VecDeque<Arc<Worker>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Schedule `Workers` for a `WorkerPool`.
|
|
|
|
|
///
|
|
|
|
|
/// Having an excessive number of Workers in WorkerPool may result in wastage of system
|
|
|
|
|
/// resources, while a shortage of workers may lead to longer response time for workitems.
|
|
|
|
|
/// A well-designed WorkerScheduler must strike a balance between resource utilization and response time.
|
|
|
|
|
pub trait WorkerScheduler: Sync + Send {
|
|
|
|
|
/// Schedule workers in a worker pool. This needs to solve two problems: when to increase or decrease
|
|
|
|
|
/// workers, and how to add or remove workers to keep the number of workers in a reasonable range.
|
|
|
|
|
fn schedule(&self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The `Monitor` is responsible for monitoring the `WorkerPool` for scheduling needs.
|
2024-10-13 13:39:47 +00:00
|
|
|
///
|
2023-10-08 16:02:24 +00:00
|
|
|
/// Currently, it only performs a liveness check, and attempts to schedule when no workers
|
|
|
|
|
/// are found processing in the pool.
|
|
|
|
|
pub struct Monitor {
|
|
|
|
|
worker_pool: Weak<WorkerPool>,
|
2024-09-13 03:13:07 +00:00
|
|
|
bound_task: Arc<Task>,
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LocalWorkerPool {
|
2024-09-23 03:51:53 +00:00
|
|
|
fn new(worker_pool: Weak<WorkerPool>, cpu_id: CpuId) -> Self {
|
2023-10-08 16:02:24 +00:00
|
|
|
LocalWorkerPool {
|
|
|
|
|
cpu_id,
|
|
|
|
|
idle_wait_queue: WaitQueue::new(),
|
|
|
|
|
parent: worker_pool,
|
|
|
|
|
heartbeat: AtomicBool::new(false),
|
|
|
|
|
workers: SpinLock::new(VecDeque::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_worker(&self) {
|
|
|
|
|
let worker = Worker::new(self.parent.clone(), self.cpu_id);
|
2024-08-09 03:32:27 +00:00
|
|
|
self.workers.disable_irq().lock().push_back(worker.clone());
|
2024-09-13 03:13:07 +00:00
|
|
|
Thread::borrow_from_task(worker.bound_task()).run();
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_worker(&self) {
|
2024-08-09 03:32:27 +00:00
|
|
|
let mut workers = self.workers.disable_irq().lock();
|
2023-10-08 16:02:24 +00:00
|
|
|
for (index, worker) in workers.iter().enumerate() {
|
|
|
|
|
if worker.is_idle() {
|
|
|
|
|
worker.destroy();
|
|
|
|
|
workers.remove(index);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn wake_worker(&self) -> bool {
|
2024-05-31 02:54:33 +00:00
|
|
|
self.idle_wait_queue.wake_one()
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn has_pending_work_items(&self) -> bool {
|
|
|
|
|
self.parent
|
|
|
|
|
.upgrade()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.has_pending_work_items(self.cpu_id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn heartbeat(&self) -> bool {
|
|
|
|
|
self.heartbeat.load(Ordering::Acquire)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_heartbeat(&self, heartbeat: bool) {
|
|
|
|
|
self.heartbeat.store(heartbeat, Ordering::Release);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn idle_current_worker(&self, worker: Arc<Worker>) {
|
|
|
|
|
self.idle_wait_queue
|
|
|
|
|
.wait_until(|| (worker.is_destroying() || self.has_pending_work_items()).then_some(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn destroy_all_workers(&self) {
|
2024-08-09 03:32:27 +00:00
|
|
|
for worker in self.workers.disable_irq().lock().iter() {
|
2023-10-08 16:02:24 +00:00
|
|
|
worker.destroy();
|
|
|
|
|
}
|
|
|
|
|
self.idle_wait_queue.wake_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WorkerPool {
|
|
|
|
|
pub fn new(priority: WorkPriority, cpu_set: CpuSet) -> Arc<Self> {
|
|
|
|
|
Arc::new_cyclic(|pool_ref| {
|
|
|
|
|
let mut local_pools = Vec::new();
|
|
|
|
|
for cpu_id in cpu_set.iter() {
|
2024-08-30 09:49:18 +00:00
|
|
|
local_pools.push(Arc::new(LocalWorkerPool::new(pool_ref.clone(), cpu_id)));
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
WorkerPool {
|
|
|
|
|
local_pools,
|
2024-06-13 06:13:59 +00:00
|
|
|
monitor: Monitor::new(pool_ref.clone(), &priority),
|
2023-10-08 16:02:24 +00:00
|
|
|
priority,
|
|
|
|
|
cpu_set,
|
|
|
|
|
scheduler: Arc::new(SimpleScheduler::new(pool_ref.clone())),
|
|
|
|
|
work_queues: SpinLock::new(Vec::new()),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(&self) {
|
|
|
|
|
self.monitor.run();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn assign_work_queue(&self, work_queue: Arc<WorkQueue>) {
|
2024-08-09 03:32:27 +00:00
|
|
|
self.work_queues.disable_irq().lock().push(work_queue);
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub fn has_pending_work_items(&self, request_cpu: CpuId) -> bool {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.work_queues
|
2024-08-09 03:32:27 +00:00
|
|
|
.disable_irq()
|
|
|
|
|
.lock()
|
2023-10-08 16:02:24 +00:00
|
|
|
.iter()
|
|
|
|
|
.any(|work_queue| work_queue.has_pending_work_items(request_cpu))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn schedule(&self) {
|
|
|
|
|
self.scheduler.schedule();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub fn num_workers(&self, cpu_id: CpuId) -> u16 {
|
2024-08-09 03:32:27 +00:00
|
|
|
self.local_pool(cpu_id).workers.disable_irq().lock().len() as u16
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cpu_set(&self) -> &CpuSet {
|
|
|
|
|
&self.cpu_set
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn fetch_pending_work_item(&self, request_cpu: CpuId) -> Option<Arc<WorkItem>> {
|
2024-08-09 03:32:27 +00:00
|
|
|
for work_queue in self.work_queues.disable_irq().lock().iter() {
|
2023-10-08 16:02:24 +00:00
|
|
|
let item = work_queue.dequeue(request_cpu);
|
|
|
|
|
if item.is_some() {
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
fn local_pool(&self, cpu_id: CpuId) -> &Arc<LocalWorkerPool> {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pools
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|local_pool: &&Arc<LocalWorkerPool>| local_pool.cpu_id == cpu_id)
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn wake_worker(&self, cpu_id: CpuId) -> bool {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).wake_worker()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn add_worker(&self, cpu_id: CpuId) {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).add_worker();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn remove_worker(&self, cpu_id: CpuId) {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).remove_worker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) fn is_high_priority(&self) -> bool {
|
|
|
|
|
self.priority == WorkPriority::High
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn heartbeat(&self, cpu_id: CpuId) -> bool {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).heartbeat()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn set_heartbeat(&self, cpu_id: CpuId, heartbeat: bool) {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).set_heartbeat(heartbeat)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 03:51:53 +00:00
|
|
|
pub(super) fn idle_current_worker(&self, cpu_id: CpuId, worker: Arc<Worker>) {
|
2023-10-08 16:02:24 +00:00
|
|
|
self.local_pool(cpu_id).idle_current_worker(worker);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for WorkerPool {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
for local_pool in self.local_pools.iter() {
|
|
|
|
|
local_pool.destroy_all_workers();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Monitor {
|
2024-06-13 06:13:59 +00:00
|
|
|
pub fn new(worker_pool: Weak<WorkerPool>, priority: &WorkPriority) -> Arc<Self> {
|
2023-10-08 16:02:24 +00:00
|
|
|
Arc::new_cyclic(|monitor_ref| {
|
|
|
|
|
let weal_monitor = monitor_ref.clone();
|
|
|
|
|
let task_fn = Box::new(move || {
|
|
|
|
|
let current_monitor: Arc<Monitor> = weal_monitor.upgrade().unwrap();
|
|
|
|
|
current_monitor.run_monitor_loop();
|
|
|
|
|
});
|
|
|
|
|
let cpu_affinity = CpuSet::new_full();
|
2024-09-14 07:32:21 +00:00
|
|
|
// FIXME: remove the use of real-time priority.
|
|
|
|
|
// Logically all monitors should be of default normal priority.
|
|
|
|
|
// This workaround is to make the monitor of high-priority worker pool
|
|
|
|
|
// starvation-free under the current scheduling policy.
|
2024-06-13 06:13:59 +00:00
|
|
|
let priority = match priority {
|
2024-09-14 07:32:21 +00:00
|
|
|
WorkPriority::High => Priority::new(PriorityRange::new(0)),
|
|
|
|
|
WorkPriority::Normal => Priority::default(),
|
2024-06-13 06:13:59 +00:00
|
|
|
};
|
2024-09-13 03:13:07 +00:00
|
|
|
let bound_task = create_new_kernel_task(
|
2023-10-08 16:02:24 +00:00
|
|
|
ThreadOptions::new(task_fn)
|
|
|
|
|
.cpu_affinity(cpu_affinity)
|
2024-06-13 06:13:59 +00:00
|
|
|
.priority(priority),
|
2023-10-08 16:02:24 +00:00
|
|
|
);
|
|
|
|
|
Self {
|
|
|
|
|
worker_pool,
|
2024-09-13 03:13:07 +00:00
|
|
|
bound_task,
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(&self) {
|
2024-09-13 03:13:07 +00:00
|
|
|
Thread::borrow_from_task(&self.bound_task).run()
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_monitor_loop(self: &Arc<Self>) {
|
2024-06-13 06:13:59 +00:00
|
|
|
let sleep_queue = WaitQueue::new();
|
|
|
|
|
let sleep_duration = Duration::from_millis(100);
|
2023-10-08 16:02:24 +00:00
|
|
|
loop {
|
|
|
|
|
let worker_pool = self.worker_pool.upgrade();
|
|
|
|
|
let Some(worker_pool) = worker_pool else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
|
|
|
|
worker_pool.schedule();
|
|
|
|
|
for local_pool in worker_pool.local_pools.iter() {
|
|
|
|
|
local_pool.set_heartbeat(false);
|
|
|
|
|
}
|
2024-09-14 02:13:14 +00:00
|
|
|
let _ = sleep_queue.wait_until_or_timeout(|| -> Option<()> { None }, &sleep_duration);
|
2023-10-08 16:02:24 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|