use std::fmt;
use std::io;
use std::sync::Arc;
use std::time::Duration;
use super::super::blocking::{Pool, PoolConfig};
use super::super::context;
use super::super::shard::Limits;
use super::super::workers::{Start, Workers};
use super::{DEFAULT, Inner, RngSeed, Runtime, RuntimeHandle, UnhandledPanic};
#[must_use]
pub struct Builder {
pub(super) worker_threads: Option<usize>,
pub(super) thread_name: Option<Arc<dyn Fn() -> String + Send + Sync>>,
pub(super) thread_stack_size: Option<usize>,
pub(super) max_nesting: Option<usize>,
pub(super) max_blocking_threads: Option<usize>,
pub(super) blocking_keep_alive: Option<Duration>,
pub(super) pin_workers: Option<bool>,
pub(super) local_spawn_margin: Option<usize>,
pub(super) io_buffer_cap: Option<usize>,
pub(super) max_worker_threads: Option<usize>,
pub(super) backend: Option<crate::IoBackend>,
pub(super) io_uring: Option<crate::config::IoUringConfiguration>,
pub(super) worker_cores: Option<Vec<usize>>,
pub(super) blocking_cores: Option<Vec<usize>>,
pub(super) handoff_after: Option<Option<Duration>>,
pub(super) start_paused: bool,
pub(super) record_latencies: bool,
#[cfg(feature = "io-stats")]
pub(super) count_io: bool,
pub(super) tuning: super::super::tuning::Tuning,
pub(super) task_hooks: super::super::tuning::TaskHooks,
pub(super) long_poll: Option<super::super::tuning::TaskHook>,
pub(super) unhandled_panic: UnhandledPanic,
pub(super) rng_seed: Option<RngSeed>,
pub(super) io: super::io_knobs::IoSettings,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
const DEFAULT_STACK: usize = 8 * 1024 * 1024;
const DEFAULT_BLOCKING_THREADS: usize = 512;
const DEFAULT_KEEP_ALIVE: Duration = Duration::from_secs(10);
const DEFAULT_LOCAL_MARGIN: usize = 32;
const DEFAULT_HANDOFF: Duration = Duration::from_millis(10);
const MIN_STACK: usize = 128 * 1024;
const MIN_HANDOFF: Duration = Duration::from_millis(1);
pub(super) const GUESTS_CAPACITY: usize = 64;
impl Builder {
fn validate(&self) -> io::Result<()> {
use crate::config::refuse;
if let Some(bytes) = self.thread_stack_size.filter(|&b| b < MIN_STACK) {
return Err(refuse(
"thread_stack_size",
bytes,
format_args!("at least {MIN_STACK} bytes"),
));
}
if let Some(depth) = self.max_nesting.filter(|&d| d < 1) {
return Err(refuse("max_nesting", depth, "at least 1"));
}
if let Some(n) = self.max_blocking_threads.filter(|&n| n < 1) {
return Err(refuse("max_blocking_threads", n, "at least 1"));
}
if let (Some(workers), Some(max)) = (self.worker_threads, self.max_worker_threads)
&& max < workers
{
return Err(refuse(
"max_worker_threads",
max,
format_args!("at least worker_threads ({workers})"),
));
}
let handoff = self.handoff_after.unwrap_or(Some(DEFAULT_HANDOFF));
if let Some(after) = handoff.filter(|&a| a < MIN_HANDOFF) {
return Err(refuse(
"handoff_after",
format_args!("{after:?}"),
format_args!("at least {MIN_HANDOFF:?}, or None"),
));
}
if self.long_poll.is_some() && handoff.is_none() {
return Err(refuse(
"on_long_poll",
"set with handoff_after(None)",
"used with handoff_after set to a duration, which is what a long poll passes",
));
}
let tuning = &self.tuning;
for (setting, runs) in [
("event_interval", tuning.driver_interval),
("global_queue_interval", tuning.inbox_interval),
] {
if runs < 1 {
return Err(refuse(setting, runs, "at least 1"));
}
}
if tuning.max_io_events < 1 {
return Err(refuse("max_io_events_per_tick", 0, "at least 1"));
}
if let Some(cfg) = &self.io_uring {
cfg.validate()?;
}
super::cores::validate("worker_cores", self.worker_cores.as_deref())?;
super::cores::validate("blocking_cores", self.blocking_cores.as_deref())?;
self.io.validate()
}
fn worker_count(&self) -> usize {
if let Some(n) = self.worker_threads {
return n;
}
let default = self.worker_cores.as_ref().map_or_else(
|| std::thread::available_parallelism().map_or(1, usize::from),
Vec::len,
);
self.max_worker_threads
.map_or(default, |max| default.min(max))
}
pub fn new() -> Self {
Self {
worker_threads: None,
thread_name: None,
thread_stack_size: None,
max_nesting: None,
max_blocking_threads: None,
blocking_keep_alive: None,
pin_workers: None,
local_spawn_margin: None,
io_buffer_cap: None,
max_worker_threads: None,
backend: None,
io_uring: None,
worker_cores: None,
blocking_cores: None,
handoff_after: None,
start_paused: false,
record_latencies: false,
#[cfg(feature = "io-stats")]
count_io: false,
tuning: super::super::tuning::Tuning::default(),
task_hooks: super::super::tuning::TaskHooks::default(),
long_poll: None,
unhandled_panic: UnhandledPanic::Ignore,
rng_seed: None,
io: super::io_knobs::IoSettings::default(),
}
}
pub fn worker_threads(mut self, n: usize) -> Self {
self.worker_threads = Some(n);
self
}
pub fn thread_name(mut self, name: impl Into<String>) -> Self {
let name: String = name.into();
self.thread_name = Some(Arc::new(move || name.clone()));
self
}
pub fn thread_name_fn(mut self, f: impl Fn() -> String + Send + Sync + 'static) -> Self {
self.thread_name = Some(Arc::new(f));
self
}
pub fn thread_stack_size(mut self, bytes: usize) -> Self {
self.thread_stack_size = Some(bytes);
self
}
pub fn max_nesting(mut self, depth: usize) -> Self {
self.max_nesting = Some(depth);
self
}
pub fn max_blocking_threads(mut self, n: usize) -> Self {
self.max_blocking_threads = Some(n);
self
}
pub fn thread_keep_alive(mut self, keep_alive: Duration) -> Self {
self.blocking_keep_alive = Some(keep_alive);
self
}
pub fn local_spawn_margin(mut self, margin: usize) -> Self {
self.local_spawn_margin = Some(margin);
self
}
pub fn max_worker_threads(mut self, n: usize) -> Self {
self.max_worker_threads = Some(n);
self
}
pub fn io_buffer_cap(mut self, bytes: usize) -> Self {
self.io_buffer_cap = Some(bytes);
self
}
pub fn backend(mut self, backend: crate::IoBackend) -> Self {
self.backend = Some(backend);
self
}
pub fn io_uring(mut self, config: crate::config::IoUringConfiguration) -> Self {
self.io_uring = Some(config);
self
}
pub fn start_paused(mut self, paused: bool) -> Self {
self.start_paused = paused;
self
}
pub fn handoff_after(mut self, after: Option<Duration>) -> Self {
self.handoff_after = Some(after);
self
}
pub fn pin_workers(mut self, pin: bool) -> Self {
self.pin_workers = Some(pin);
self
}
pub fn record_latencies(mut self, record: bool) -> Self {
self.record_latencies = record;
self
}
#[cfg(feature = "io-stats")]
pub fn count_io(mut self, count: bool) -> Self {
self.count_io = count;
self
}
pub fn build(self) -> io::Result<Runtime> {
self.validate()?;
let workers = self.worker_count();
let stack = self.thread_stack_size.unwrap_or(DEFAULT_STACK);
let limits = Limits {
max_nesting: self.max_nesting.unwrap_or(context::DEFAULT_MAX_NESTING),
buffer_cap: self.io_buffer_cap.unwrap_or(crate::buf::DEFAULT_CAP),
backend: super::super::backend::for_runtime(self.backend, self.io_uring.as_ref())?,
};
#[cfg(ring)]
let rings = limits.backend.rings();
let blocking_name = self
.thread_name
.clone()
.unwrap_or_else(|| Arc::new(|| "nuclei-blocking".to_string()));
let worker_name = self
.thread_name
.unwrap_or_else(|| Arc::new(|| "nuclei-worker".to_string()));
let pool = Pool::new(PoolConfig {
hooks: self.tuning.hooks.clone(),
max_threads: self
.max_blocking_threads
.unwrap_or(DEFAULT_BLOCKING_THREADS)
.max(1),
keep_alive: self.blocking_keep_alive.unwrap_or(DEFAULT_KEEP_ALIVE),
stack_size: self.thread_stack_size,
name: blocking_name,
cpu_mask: match &self.blocking_cores {
Some(cores) => super::cores::mask_of(cores),
None => super::current_mask(),
},
});
static IDS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
let (exit_tx, exits) = kovan_channel::unbounded();
let pin = self.pin_workers.unwrap_or(true);
let (cores, mask) = match &self.worker_cores {
Some(list) if pin => (list.clone(), None),
Some(list) => (Vec::new(), super::cores::mask_of(list)),
None if pin => (super::allowed_cores(), None),
None => (Vec::new(), None),
};
let capacity = self
.max_worker_threads
.unwrap_or(workers.saturating_mul(4))
.max(workers);
let shutdown_on_panic = self.unhandled_panic == UnhandledPanic::ShutdownRuntime;
let mut task_hooks = self.task_hooks;
super::super::notes::compose(
&mut task_hooks,
self.long_poll.is_some(),
self.record_latencies,
);
let inner = Arc::new_cyclic(|this| {
if shutdown_on_panic {
task_hooks.shutdown = Some(this.clone());
}
Inner {
id: IDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
workers: Workers::new(
capacity,
workers,
Start {
limits,
tuning: super::super::tuning::Tuning {
blocking: Some(pool.jobs_counter()),
record_latencies: self.record_latencies,
#[cfg(feature = "io-stats")]
count_io: self.count_io,
..self.tuning.clone()
},
seed: self.rng_seed,
stack,
name: worker_name,
cores,
mask,
handoff_after: self.handoff_after.unwrap_or(Some(DEFAULT_HANDOFF)),
long_poll: self.long_poll,
},
exit_tx,
),
pool,
local_margin: self.local_spawn_margin.unwrap_or(DEFAULT_LOCAL_MARGIN),
#[cfg(ring)]
rings,
clock: crate::time::clock::Clock::new(self.start_paused),
task_hooks: task_hooks.any().then(|| {
super::super::tuning::ANY_TASK_HOOKS
.store(true, std::sync::atomic::Ordering::Release);
Arc::new(task_hooks)
}),
shutdown_on_panic,
panicked: std::sync::atomic::AtomicBool::new(false),
on_panic: crate::sync::Notify::new(),
rng_seed: self.rng_seed,
guests: std::sync::OnceLock::new(),
lingering: super::super::linger::Lingering::new(),
io: self.io.resolve(),
}
});
inner.pool.set_owner(Arc::downgrade(&inner));
let runtime = Runtime { inner, exits };
Workers::start(&runtime.inner, workers)?;
Ok(runtime)
}
pub fn build_global(self) -> io::Result<RuntimeHandle> {
if DEFAULT.get().is_some() {
return Err(already_running());
}
let runtime = self.build()?;
let handle = runtime.handle();
DEFAULT.set(runtime).map_err(|_| already_running())?;
Ok(handle)
}
}
fn already_running() -> io::Error {
io::Error::new(
io::ErrorKind::AlreadyExists,
"the default runtime is already running",
)
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct("Builder");
builder
.field("worker_threads", &self.worker_threads)
.field("thread_stack_size", &self.thread_stack_size)
.field("max_nesting", &self.max_nesting)
.field("max_blocking_threads", &self.max_blocking_threads)
.field("thread_keep_alive", &self.blocking_keep_alive)
.field("pin_workers", &self.pin_workers)
.field("worker_cores", &self.worker_cores)
.field("blocking_cores", &self.blocking_cores)
.field("local_spawn_margin", &self.local_spawn_margin)
.field("io_buffer_cap", &self.io_buffer_cap)
.field("io_uring", &self.io_uring)
.field("start_paused", &self.start_paused)
.field("record_latencies", &self.record_latencies);
#[cfg(feature = "io-stats")]
builder.field("count_io", &self.count_io);
builder.finish_non_exhaustive()
}
}