use std::sync::atomic::AtomicBool;
use std::task::Waker;
use std::thread;
use many_cpus::{ProcessorSet, SystemHardware};
use nonempty::NonEmpty;
use observed::{Sink, emit};
use performables::arc::Arc;
use performables::sync::channel;
use performables::sync::channel::{OneshotReceiver, Sender};
use thread_aware::{Thread, ThreadAware, ThreadBuilder};
use tick::runtime::InactiveClock;
use crate::runtime::blocking_worker::BlockingWorker;
use crate::runtime::bootstrap::pools::BlockingPools;
use crate::runtime::config::RuntimeConfig;
use crate::runtime::dispatch::{DispatcherClient, DispatcherCore, WorkerEndpoint};
use crate::runtime::error::Error;
use crate::runtime::handle::Runtime;
use crate::runtime::telemetry::events::{
AsyncWorkerActive, AsyncWorkerStarted, AsyncWorkerStopped, BlockingWorkerPoolMode, RuntimeStartFailed, RuntimeStarted,
};
use crate::runtime::thread::spawn;
use crate::runtime::thread::waiter::ThreadWaiter;
use crate::runtime::worker::AsyncWorker;
use crate::runtime::worker::protocol::AsyncWorkerCommand;
use crate::runtime::worker::signal::WorkerSignal;
pub(crate) fn build(
processor_config: RuntimeConfig,
clock: &InactiveClock,
sink: Sink,
thread_builder: &ThreadBuilder,
) -> Result<Runtime, Error> {
let pool_mode = processor_config.blocking_pool_policy.mode_label();
let available = available_processors();
let processors = processor_config.workers_policy.select(&available).inspect_err(|_| {
emit!(
&sink,
RuntimeStartFailed {
blocking_worker_pool_mode: BlockingWorkerPoolMode(pool_mode),
}
);
})?;
let mut processors: Vec<_> = processors.decompose().into_iter().collect();
processors.sort_by_key(|processor| processor.processors().first().id());
let mut async_worker_command_txs = Vec::with_capacity(processors.len());
let mut async_worker_start_txs = Vec::with_capacity(processors.len());
let mut async_worker_success_rxs = Vec::with_capacity(processors.len());
let mut async_worker_join_handles = Vec::with_capacity(processors.len());
let stack_size = processor_config
.stack_size
.get()
.max(std::env::var("RUST_MIN_STACK").unwrap_or_default().parse().unwrap_or(0));
let worker_count = processors.len();
let shutdown_started = Arc::new(AtomicBool::new(false));
let blocking_pools = processor_config.blocking_pool_policy.into_pools().inspect_err(|_| {
emit!(
&sink,
RuntimeStartFailed {
blocking_worker_pool_mode: BlockingWorkerPoolMode(pool_mode),
}
);
})?;
for (worker_index, processor) in processors.into_iter().enumerate() {
let (command_tx, command_rx) = channel::unbounded();
let (worker_endpoint_tx, worker_endpoint_rx) = channel::unbounded();
let (start_tx, start_rx) = channel::oneshot();
let (success_tx, success_rx) = channel::unbounded();
async_worker_start_txs.push(start_tx);
async_worker_join_handles.push(
AsyncWorkerStartInfo {
command_rx,
stack_size,
start_rx,
success_tx,
inactive_clock: clock.clone(),
processor,
worker_index,
worker_endpoint_tx,
thread_builder: thread_builder.clone(),
blocking_pools: blocking_pools.clone(),
shutdown_started: Arc::clone(&shutdown_started),
sink: sink.clone(),
}
.start(),
);
let (waker, thread, blocking_worker) = worker_endpoint_rx
.recv()
.expect("failed to receive the worker endpoint from the starting worker");
async_worker_command_txs.push(WorkerEndpoint {
command_tx,
waker,
thread,
blocking_worker,
});
async_worker_success_rxs.push(success_rx);
}
let runtime_sink = sink.clone();
let dispatcher = Arc::new(DispatcherCore::new_with_shutdown(
ThreadWaiter::new(async_worker_join_handles),
NonEmpty::from_vec(async_worker_command_txs)
.expect("the number is either hardcoded or validated in the builder, so can never be zero"),
sink,
shutdown_started,
));
let dispatcher_client = DispatcherClient::new(dispatcher);
for start_tx in async_worker_start_txs {
start_tx
.send(StartWorker {
dispatcher: dispatcher_client.clone(),
})
.expect("a starting worker retains its start receiver");
}
for success_rx in async_worker_success_rxs {
success_rx.recv().expect("failed to receive worker startup signal");
}
emit!(
runtime_sink,
RuntimeStarted {
processors_available: available.len().into(),
processors_used: worker_count.into(),
blocking_worker_pool_mode: BlockingWorkerPoolMode(pool_mode),
stack_size_bytes: stack_size.into(),
}
);
Ok(Runtime::with_dispatcher(dispatcher_client))
}
#[derive(Debug)]
struct AsyncWorkerStartInfo {
command_rx: channel::Receiver<AsyncWorkerCommand>,
stack_size: usize,
start_rx: OneshotReceiver<StartWorker>,
success_tx: Sender<()>,
inactive_clock: InactiveClock,
processor: ProcessorSet,
worker_index: usize,
worker_endpoint_tx: Sender<(Waker, Thread, Arc<BlockingWorker>)>,
thread_builder: ThreadBuilder,
blocking_pools: BlockingPools,
shutdown_started: Arc<AtomicBool>,
sink: Sink,
}
impl AsyncWorkerStartInfo {
fn start(self) -> thread::JoinHandle<()> {
let thread_name = format!("oxidizer-rt-{}", self.worker_index);
let thread_lifecycle_sink = self.sink.clone();
let stack_size = self.stack_size;
spawn(&thread_lifecycle_sink, &thread_name, Some(stack_size), move || {
self.run_worker_thread();
})
}
fn run_worker_thread(self) {
let Self {
command_rx,
stack_size: _stack_size,
start_rx,
success_tx,
inactive_clock,
processor,
worker_index,
worker_endpoint_tx,
thread_builder,
blocking_pools,
shutdown_started,
sink,
} = self;
let worker_sink = sink.clone();
let thread_builder = thread_builder.with_numa_node(processor.processors().first().memory_region_id());
processor.pin_current_thread_to();
let current = thread_builder.build(thread::current().id());
let mut clock = inactive_clock;
clock.relocate(None, ¤t);
let blocking_worker =
BlockingWorker::new_with_shutdown(blocking_pools.build_worker(), worker_sink.clone(), Arc::clone(&shutdown_started));
let signal = Arc::new(WorkerSignal::default());
let worker_waker = WorkerSignal::waker(&signal);
worker_endpoint_tx
.send((worker_waker, current.clone(), Arc::clone(&blocking_worker)))
.expect("failed to send the worker endpoint to the runtime builder");
let start_signal = futures::executor::block_on(start_rx)
.expect("RuntimeBuilder failed between initializing and starting a worker - impossible to continue worker execution");
let worker = AsyncWorker::new(
command_rx,
start_signal.dispatcher,
current,
sink,
Arc::clone(&blocking_worker),
clock,
signal,
);
emit!(
worker_sink,
AsyncWorkerStarted {
processor_index: worker_index.into(),
}
);
emit!(worker_sink, AsyncWorkerActive { delta: 1 });
_ = success_tx.send(());
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| worker.run()));
if outcome.is_ok() {
emit!(
worker_sink,
AsyncWorkerStopped {
processor_index: worker_index.into(),
}
);
}
emit!(worker_sink, AsyncWorkerActive { delta: -1 });
blocking_worker.join();
if let Err(panic) = outcome {
std::panic::resume_unwind(panic);
}
}
}
#[derive(Debug)]
struct StartWorker {
dispatcher: DispatcherClient,
}
fn available_processors() -> ProcessorSet {
#[cfg(all(miri, any(test, feature = "test-util")))]
{
use std::num::NonZeroUsize;
SystemHardware::fake(many_cpus::fake::HardwareBuilder::from_counts(
NonZeroUsize::new(6).expect("six is nonzero"),
NonZeroUsize::new(2).expect("two is nonzero"),
))
.processors()
}
#[cfg(not(all(miri, any(test, feature = "test-util"))))]
{
SystemHardware::current().processors()
}
}