mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2025-03-12 16:11:43 +01:00
224 lines
5.9 KiB
Rust
224 lines
5.9 KiB
Rust
|
//!
|
||
|
//! Pool of threads to run lightweight processes
|
||
|
//!
|
||
|
//! We spawn futures onto the pool with [`spawn`] method of global run queue or
|
||
|
//! with corresponding [`Worker`]'s spawn method.
|
||
|
//!
|
||
|
//! [`spawn`]: crate::pool::spawn
|
||
|
//! [`Worker`]: crate::run_queue::Worker
|
||
|
|
||
|
use crate::thread_manager::{DynamicPoolManager, DynamicRunner};
|
||
|
use crate::worker;
|
||
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||
|
use lazy_static::lazy_static;
|
||
|
use lightproc::lightproc::LightProc;
|
||
|
use lightproc::recoverable_handle::RecoverableHandle;
|
||
|
use once_cell::sync::{Lazy, OnceCell};
|
||
|
use std::future::Future;
|
||
|
use std::iter::Iterator;
|
||
|
use std::time::Duration;
|
||
|
use std::{env, thread};
|
||
|
use tracing::trace;
|
||
|
|
||
|
///
|
||
|
/// Spawn a process (which contains future + process stack) onto the executor from the global level.
|
||
|
///
|
||
|
/// # Example
|
||
|
/// ```rust
|
||
|
/// use executor::prelude::*;
|
||
|
///
|
||
|
/// # #[cfg(feature = "tokio-runtime")]
|
||
|
/// # #[tokio::main]
|
||
|
/// # async fn main() {
|
||
|
/// # start();
|
||
|
/// # }
|
||
|
/// #
|
||
|
/// # #[cfg(not(feature = "tokio-runtime"))]
|
||
|
/// # fn main() {
|
||
|
/// # start();
|
||
|
/// # }
|
||
|
/// #
|
||
|
/// # fn start() {
|
||
|
///
|
||
|
/// let handle = spawn(
|
||
|
/// async {
|
||
|
/// panic!("test");
|
||
|
/// },
|
||
|
/// );
|
||
|
///
|
||
|
/// run(
|
||
|
/// async {
|
||
|
/// handle.await;
|
||
|
/// },
|
||
|
/// ProcStack { },
|
||
|
/// );
|
||
|
/// # }
|
||
|
/// ```
|
||
|
pub fn spawn<F, R>(future: F) -> RecoverableHandle<R>
|
||
|
where
|
||
|
F: Future<Output = R> + Send + 'static,
|
||
|
R: Send + 'static,
|
||
|
{
|
||
|
let (task, handle) = LightProc::recoverable(future, worker::schedule);
|
||
|
task.schedule();
|
||
|
handle
|
||
|
}
|
||
|
|
||
|
/// Spawns a blocking task.
|
||
|
///
|
||
|
/// The task will be spawned onto a thread pool specifically dedicated to blocking tasks.
|
||
|
pub fn spawn_blocking<F, R>(future: F) -> RecoverableHandle<R>
|
||
|
where
|
||
|
F: Future<Output = R> + Send + 'static,
|
||
|
R: Send + 'static,
|
||
|
{
|
||
|
let (task, handle) = LightProc::recoverable(future, schedule);
|
||
|
task.schedule();
|
||
|
handle
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Acquire the static Pool reference
|
||
|
#[inline]
|
||
|
pub fn get() -> &'static Pool {
|
||
|
&*POOL
|
||
|
}
|
||
|
|
||
|
pub fn get_manager() -> Option<&'static DynamicPoolManager<AsyncRunner>> {
|
||
|
DYNAMIC_POOL_MANAGER.get()
|
||
|
}
|
||
|
|
||
|
impl Pool {
|
||
|
///
|
||
|
/// Spawn a process (which contains future + process stack) onto the executor via [Pool] interface.
|
||
|
pub fn spawn<F, R>(&self, future: F) -> RecoverableHandle<R>
|
||
|
where
|
||
|
F: Future<Output = R> + Send + 'static,
|
||
|
R: Send + 'static,
|
||
|
{
|
||
|
let (task, handle) = LightProc::recoverable(future, worker::schedule);
|
||
|
task.schedule();
|
||
|
handle
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Enqueues work, attempting to send to the thread pool in a
|
||
|
/// nonblocking way and spinning up needed amount of threads
|
||
|
/// based on the previous statistics without relying on
|
||
|
/// if there is not a thread ready to accept the work or not.
|
||
|
pub(crate) fn schedule(t: LightProc) {
|
||
|
if let Err(err) = POOL.sender.try_send(t) {
|
||
|
// We were not able to send to the channel without
|
||
|
// blocking.
|
||
|
POOL.sender.send(err.into_inner()).unwrap();
|
||
|
}
|
||
|
// Add up for every incoming scheduled task
|
||
|
DYNAMIC_POOL_MANAGER.get().unwrap().increment_frequency();
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Low watermark value, defines the bare minimum of the pool.
|
||
|
/// Spawns initial thread set.
|
||
|
/// Can be configurable with env var `BASTION_BLOCKING_THREADS` at runtime.
|
||
|
#[inline]
|
||
|
fn low_watermark() -> &'static u64 {
|
||
|
lazy_static! {
|
||
|
static ref LOW_WATERMARK: u64 = {
|
||
|
env::var_os("BASTION_BLOCKING_THREADS")
|
||
|
.map(|x| x.to_str().unwrap().parse::<u64>().unwrap())
|
||
|
.unwrap_or(DEFAULT_LOW_WATERMARK)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
&*LOW_WATERMARK
|
||
|
}
|
||
|
|
||
|
/// If low watermark isn't configured this is the default scaler value.
|
||
|
/// This value is used for the heuristics of the scaler
|
||
|
const DEFAULT_LOW_WATERMARK: u64 = 2;
|
||
|
|
||
|
/// Pool interface between the scheduler and thread pool
|
||
|
#[derive(Debug)]
|
||
|
pub struct Pool {
|
||
|
sender: Sender<LightProc>,
|
||
|
receiver: Receiver<LightProc>,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
pub struct AsyncRunner {
|
||
|
|
||
|
}
|
||
|
|
||
|
impl DynamicRunner for AsyncRunner {
|
||
|
fn run_static(&self, park_timeout: Duration) -> ! {
|
||
|
loop {
|
||
|
for task in &POOL.receiver {
|
||
|
trace!("static: running task");
|
||
|
self.run(task);
|
||
|
}
|
||
|
|
||
|
trace!("static: empty queue, parking with timeout");
|
||
|
thread::park_timeout(park_timeout);
|
||
|
}
|
||
|
}
|
||
|
fn run_dynamic(&self, parker: impl Fn()) -> ! {
|
||
|
loop {
|
||
|
while let Ok(task) = POOL.receiver.try_recv() {
|
||
|
trace!("dynamic thread: running task");
|
||
|
self.run(task);
|
||
|
}
|
||
|
trace!(
|
||
|
"dynamic thread: parking - {:?}",
|
||
|
std::thread::current().id()
|
||
|
);
|
||
|
parker();
|
||
|
}
|
||
|
}
|
||
|
fn run_standalone(&self) {
|
||
|
while let Ok(task) = POOL.receiver.try_recv() {
|
||
|
self.run(task);
|
||
|
}
|
||
|
trace!("standalone thread: quitting.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl AsyncRunner {
|
||
|
fn run(&self, task: LightProc) {
|
||
|
task.run();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static DYNAMIC_POOL_MANAGER: OnceCell<DynamicPoolManager<AsyncRunner>> = OnceCell::new();
|
||
|
|
||
|
static POOL: Lazy<Pool> = Lazy::new(|| {
|
||
|
#[cfg(feature = "tokio-runtime")]
|
||
|
{
|
||
|
let runner = AsyncRunner {
|
||
|
// We use current() here instead of try_current()
|
||
|
// because we want bastion to crash as soon as possible
|
||
|
// if there is no available runtime.
|
||
|
runtime_handle: tokio::runtime::Handle::current(),
|
||
|
};
|
||
|
|
||
|
DYNAMIC_POOL_MANAGER
|
||
|
.set(DynamicPoolManager::new(*low_watermark() as usize, runner))
|
||
|
.expect("couldn't create dynamic pool manager");
|
||
|
}
|
||
|
#[cfg(not(feature = "tokio-runtime"))]
|
||
|
{
|
||
|
let runner = AsyncRunner {};
|
||
|
|
||
|
DYNAMIC_POOL_MANAGER
|
||
|
.set(DynamicPoolManager::new(*low_watermark() as usize, runner))
|
||
|
.expect("couldn't create dynamic pool manager");
|
||
|
}
|
||
|
|
||
|
DYNAMIC_POOL_MANAGER
|
||
|
.get()
|
||
|
.expect("couldn't get static pool manager")
|
||
|
.initialize();
|
||
|
|
||
|
let (sender, receiver) = unbounded();
|
||
|
Pool { sender, receiver }
|
||
|
});
|