use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::io;
use std::sync::{Arc, OnceLock, Weak};
use super::shard::Shared;
use crate::IoBackend;
use crate::stats::{ErrorCounts, ShardStats, Stats};
pub(crate) const KINDS: [io::ErrorKind; 39] = {
use io::ErrorKind::*;
[
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
HostUnreachable,
NetworkUnreachable,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
NetworkDown,
BrokenPipe,
AlreadyExists,
WouldBlock,
NotADirectory,
IsADirectory,
DirectoryNotEmpty,
ReadOnlyFilesystem,
StaleNetworkFileHandle,
InvalidInput,
InvalidData,
TimedOut,
WriteZero,
StorageFull,
NotSeekable,
QuotaExceeded,
FileTooLarge,
ResourceBusy,
ExecutableFileBusy,
Deadlock,
CrossesDevices,
TooManyLinks,
InvalidFilename,
ArgumentListTooLong,
Interrupted,
Unsupported,
UnexpectedEof,
OutOfMemory,
Other,
]
};
pub(crate) fn kind_index(kind: io::ErrorKind) -> usize {
KINDS
.iter()
.position(|&k| k == kind)
.unwrap_or(KINDS.len() - 1)
}
#[repr(align(128))]
pub(crate) struct Counters {
polls: AtomicU64,
parks: AtomicU64,
parked_nanos: AtomicU64,
queue_depth: AtomicUsize,
born: std::time::Instant,
remote_wakes: AtomicU64,
timers: AtomicUsize,
buffer_bytes: AtomicUsize,
buffer_exhausted: AtomicU64,
ops_in_flight: AtomicUsize,
cq_overflows: AtomicU64,
handoffs: AtomicU64,
longest_poll_nanos: AtomicU64,
worker: Option<usize>,
gone: AtomicBool,
backend: IoBackend,
buffer_cap: usize,
errors: Errors,
}
pub(crate) struct Published {
pub(crate) polls: u64,
pub(crate) parks: u64,
pub(crate) parked: std::time::Duration,
pub(crate) queue_depth: usize,
pub(crate) remote_wakes: u64,
pub(crate) timers: usize,
pub(crate) buffer_bytes: usize,
pub(crate) buffer_exhausted: u64,
pub(crate) ops_in_flight: usize,
pub(crate) cq_overflows: u64,
}
impl Counters {
pub(crate) fn new(backend: IoBackend, buffer_cap: usize, worker: Option<usize>) -> Self {
Self {
polls: AtomicU64::new(0),
parks: AtomicU64::new(0),
parked_nanos: AtomicU64::new(0),
queue_depth: AtomicUsize::new(0),
born: std::time::Instant::now(),
remote_wakes: AtomicU64::new(0),
timers: AtomicUsize::new(0),
buffer_bytes: AtomicUsize::new(0),
buffer_exhausted: AtomicU64::new(0),
ops_in_flight: AtomicUsize::new(0),
cq_overflows: AtomicU64::new(0),
handoffs: AtomicU64::new(0),
longest_poll_nanos: AtomicU64::new(0),
worker,
gone: AtomicBool::new(false),
backend,
buffer_cap,
errors: Errors::new(),
}
}
pub(crate) fn publish(&self, now: Published) {
self.polls.store(now.polls, Ordering::Relaxed);
self.parks.store(now.parks, Ordering::Relaxed);
let parked = u64::try_from(now.parked.as_nanos()).unwrap_or(u64::MAX);
self.parked_nanos.store(parked, Ordering::Relaxed);
self.queue_depth.store(now.queue_depth, Ordering::Relaxed);
self.remote_wakes.store(now.remote_wakes, Ordering::Relaxed);
self.timers.store(now.timers, Ordering::Relaxed);
self.buffer_bytes.store(now.buffer_bytes, Ordering::Relaxed);
self.buffer_exhausted
.store(now.buffer_exhausted, Ordering::Relaxed);
self.ops_in_flight
.store(now.ops_in_flight, Ordering::Relaxed);
self.cq_overflows.store(now.cq_overflows, Ordering::Relaxed);
}
pub(crate) fn handed_off(&self) {
self.handoffs.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn longest_poll(&self, length: std::time::Duration) {
let nanos = u64::try_from(length.as_nanos()).unwrap_or(u64::MAX);
self.longest_poll_nanos.fetch_max(nanos, Ordering::Relaxed);
}
pub(crate) fn went(&self) {
self.gone.store(true, Ordering::Relaxed);
}
fn is_gone(&self) -> bool {
self.gone.load(Ordering::Relaxed)
}
pub(crate) fn refused_registration(&self) {
self.errors
.refused_registrations
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn read(&self, tasks: usize) -> ShardStats {
ShardStats {
worker: self.worker,
backend: self.backend,
tasks,
polls: self.polls.load(Ordering::Relaxed),
parks: self.parks.load(Ordering::Relaxed),
parked: std::time::Duration::from_nanos(self.parked_nanos.load(Ordering::Relaxed)),
busy: self
.born
.elapsed()
.saturating_sub(std::time::Duration::from_nanos(
self.parked_nanos.load(Ordering::Relaxed),
)),
queue_depth: self.queue_depth.load(Ordering::Relaxed),
remote_wakes: self.remote_wakes.load(Ordering::Relaxed),
ops_in_flight: self.ops_in_flight.load(Ordering::Relaxed),
cq_overflows: self.cq_overflows.load(Ordering::Relaxed),
buffer_bytes: self.buffer_bytes.load(Ordering::Relaxed),
buffer_cap: self.buffer_cap,
buffer_exhausted: self.buffer_exhausted.load(Ordering::Relaxed),
timers: self.timers.load(Ordering::Relaxed),
handoffs: self.handoffs.load(Ordering::Relaxed),
longest_poll: std::time::Duration::from_nanos(
self.longest_poll_nanos.load(Ordering::Relaxed),
),
errors: self.errors.read(),
}
}
}
struct Errors {
by_kind: [AtomicU64; KINDS.len()],
refused_registrations: AtomicU64,
accepts_out_of_descriptors: AtomicU64,
}
impl Errors {
const fn new() -> Self {
Self {
by_kind: [const { AtomicU64::new(0) }; KINDS.len()],
refused_registrations: AtomicU64::new(0),
accepts_out_of_descriptors: AtomicU64::new(0),
}
}
fn read(&self) -> ErrorCounts {
ErrorCounts {
by_kind: core::array::from_fn(|i| self.by_kind[i].load(Ordering::Relaxed)),
refused_registrations: self.refused_registrations.load(Ordering::Relaxed),
accepts_out_of_descriptors: self.accepts_out_of_descriptors.load(Ordering::Relaxed),
}
}
}
static OFF_SHARD: Errors = Errors::new();
fn errors_here(f: impl FnOnce(&Errors)) {
super::context::current(|shard| match shard {
Some(shard) => f(&shard.shared.counters.errors),
None => f(&OFF_SHARD),
});
}
pub(crate) fn io_error(err: &io::Error) {
if err.kind() == io::ErrorKind::WouldBlock {
return;
}
let at = kind_index(err.kind());
errors_here(|errors| {
errors.by_kind[at].fetch_add(1, Ordering::Relaxed);
});
}
pub(crate) fn accept_error(err: &io::Error) {
if out_of_descriptors(err) {
errors_here(|errors| {
errors
.accepts_out_of_descriptors
.fetch_add(1, Ordering::Relaxed);
});
}
}
fn out_of_descriptors(err: &io::Error) -> bool {
#[cfg(unix)]
let codes = [libc::EMFILE, libc::ENFILE];
#[cfg(windows)]
let codes = [crate::syscore::windows::WSAEMFILE];
err.raw_os_error().is_some_and(|code| codes.contains(&code))
}
fn registry() -> &'static kovan_map::HashMap<usize, Weak<Shared>> {
static SHARDS: OnceLock<kovan_map::HashMap<usize, Weak<Shared>>> = OnceLock::new();
SHARDS.get_or_init(|| kovan_map::HashMap::with_capacity(64))
}
pub(crate) fn register(shared: &Arc<Shared>) {
take_out_gone();
registry().insert(Arc::as_ptr(shared) as usize, Arc::downgrade(shared));
}
fn take_out_gone() {
let gone: Vec<usize> = registry()
.iter()
.filter(|(_, shard)| shard.upgrade().is_none_or(|s| s.counters.is_gone()))
.map(|(key, _)| key)
.collect();
for key in gone {
drop(registry().remove(&key));
}
}
pub(crate) fn snapshot() -> Stats {
take_out_gone();
let mut shards: Vec<ShardStats> = registry()
.values()
.filter_map(|shared| shared.upgrade())
.filter(|shared| !shared.counters.is_gone())
.map(|shared| shared.stats())
.collect();
shards.sort_by_key(|shard| (shard.worker.is_none(), shard.worker));
Stats {
shards,
blocking_threads: super::blocking::threads_alive(),
off_shard_errors: OFF_SHARD.read(),
}
}
#[cfg(all(test, not(loom)))]
mod tests;