#[cfg(completion)]
pub(crate) mod ops;
#[cfg(port)]
pub(crate) mod port;
mod read;
pub(crate) mod requests;
#[cfg(ring)]
pub(crate) mod ring;
pub(crate) mod write;
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
use std::io;
use std::net::SocketAddr;
#[cfg(unix)]
use std::os::unix::net as unix;
use std::sync::Arc;
use std::task::{Poll, Wake};
use self::read::ReadState;
use self::requests::Ask;
use self::write::WriteState;
use super::raw::{AtomicRaw, Raw};
use super::side::{After, Hooks, Side};
use crate::rt::context;
#[cfg(readiness)]
use crate::rt::park::Readiness;
use crate::rt::shard::{IoOp, Shared};
#[derive(Clone, Copy)]
pub(crate) enum Io<'a> {
#[cfg(readiness)]
Fd(Raw),
#[cfg(completion)]
Ops(route::Route<'a>),
#[cfg(not(completion))]
#[expect(dead_code, reason = "uninhabited: it only carries the lifetime")]
None(core::convert::Infallible, core::marker::PhantomData<&'a ()>),
}
#[cfg(completion)]
pub(crate) mod route {
#[cfg(port)]
pub(crate) use super::port::PortIo as Route;
#[cfg(ring)]
pub(crate) use super::ring::RingIo as Route;
}
const NO_FD: Raw = super::raw::NONE;
const NO_TOKEN: u64 = u64::MAX;
const UNREGISTERED: u8 = 0;
const REGISTERING: u8 = 1;
const REGISTERED: u8 = 2;
mod read_work {
pub(super) const UNSURE: u8 = 1;
pub(super) const UNSTARVE: u8 = 2;
pub(super) const ABANDON: u8 = 4;
#[cfg(completion)]
pub(super) const DONE: u8 = 8;
#[cfg(completion)]
pub(super) const SUBMIT: u8 = 16;
#[cfg(ring)]
pub(super) const ARRIVALS: u8 = 32;
#[cfg(port)]
pub(super) const LOOK: u8 = 64;
}
mod write_work {
pub(super) const UNSTARVE: u8 = 1;
pub(super) const ABANDON: u8 = 2;
#[cfg(completion)]
pub(super) const DONE: u8 = 4;
#[cfg(completion)]
pub(super) const SUBMIT: u8 = 8;
#[cfg(port)]
pub(super) const LOOK: u8 = 16;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Kind {
#[cfg(unix)]
Stream,
Tcp,
Bytes,
File,
TcpListener,
#[cfg(unix)]
UnixListener,
Udp,
#[cfg(unix)]
UnixDatagram,
#[cfg(unix)]
Fd,
}
impl Kind {
#[cfg_attr(
not(completion),
expect(dead_code, reason = "the completion drivers' only")
)]
pub(crate) fn listens(self) -> bool {
match self {
Kind::TcpListener => true,
#[cfg(unix)]
Kind::UnixListener => true,
_ => false,
}
}
pub(crate) fn streams(self) -> bool {
match self {
Kind::Tcp => true,
#[cfg(unix)]
Kind::Stream => true,
_ => false,
}
}
pub(crate) fn datagrams(self) -> bool {
match self {
Kind::Udp => true,
#[cfg(unix)]
Kind::UnixDatagram => true,
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum Addr {
Inet(SocketAddr),
#[cfg(unix)]
Unix(unix::SocketAddr),
}
#[derive(Clone, Debug)]
pub(crate) enum Target {
Connected,
Inet(SocketAddr),
#[cfg(unix)]
Unix(crate::io::sys::UnixAddr),
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Failure {
code: Option<i32>,
kind: io::ErrorKind,
}
impl Failure {
pub(crate) fn of(err: &io::Error) -> Self {
Self {
code: err.raw_os_error(),
kind: err.kind(),
}
}
pub(crate) fn error(self) -> io::Error {
match self.code {
Some(code) => io::Error::from_raw_os_error(code),
None => self.kind.into(),
}
}
}
struct Registration {
state: AtomicU8,
token: AtomicU64,
shard: UnsafeCell<Option<Arc<Shared>>>,
#[cfg(completion)]
ops: AtomicBool,
#[cfg(ring)]
slot: AtomicU32,
home: core::sync::atomic::AtomicUsize,
#[cfg(port)]
awaits_idle: AtomicBool,
}
pub(crate) struct Source {
fd: AtomicRaw,
retired_fd: AtomicRaw,
epoch: AtomicU32,
retired_epoch: AtomicU32,
kind: Kind,
read: Side<ReadState>,
write: Side<WriteState>,
reg: Registration,
requests: AtomicU8,
ask: Ask,
unsent: AtomicBool,
#[cfg(completion)]
read_done: ops::Mailbox,
#[cfg(completion)]
write_done: ops::Mailbox,
#[cfg(ring)]
read_arrivals: kovan_queue::seg_queue::SegQueue<(ring::Arrival, u32)>,
#[cfg(completion)]
ops_out: core::sync::atomic::AtomicU8,
#[cfg(port)]
port: port::PortState,
}
unsafe impl Send for Source {}
unsafe impl Sync for Source {}
#[derive(Clone, Copy)]
struct SourceHooks<'a> {
src: &'a Arc<Source>,
#[cfg(readiness)]
token: Option<u64>,
}
impl Hooks<ReadState> for SourceHooks<'_> {
fn taken(&self, st: &mut ReadState, after: &mut After) {
if self.src.catch_up(st.epoch_mut()) {
st.discard(after);
}
}
fn serve(&self, st: &mut ReadState, after: &mut After) {
#[cfg(readiness)]
if let Some(fd) = self.src.serving_fd(self.token) {
st.serve(fd, self.src.kind, true, after);
}
#[cfg(not(readiness))]
let _ = (st, after);
}
fn deferred(&self, st: &mut ReadState, bits: u8, after: &mut After) {
if bits & read_work::UNSURE != 0 {
st.short_reads_unsure();
}
if bits & read_work::UNSTARVE != 0 {
st.unstarve(after);
}
if bits & read_work::ABANDON != 0 {
st.abandon_lent(after);
}
#[cfg(completion)]
{
#[cfg(ring)]
if bits & (read_work::ARRIVALS | read_work::DONE) != 0 {
while let Ok((arrival, epoch)) = self.src.read_arrivals.try_pop() {
st.take_arrival(arrival, epoch, after);
}
}
if bits & read_work::DONE != 0
&& let Some(done) = self.src.read_done.take()
{
st.op_done(done, self.src.serving_fd(None), after);
}
if bits & (read_work::DONE | read_work::SUBMIT) != 0 {
self.src
.with_route(|io| st.submit_wanted(io, self.src.kind, after));
}
}
#[cfg(port)]
if bits & read_work::LOOK != 0 {
st.wake_all(after);
}
}
fn requests(&self, st: &mut ReadState, after: &mut After) {
self.src.requests_holding_read(st, after);
}
}
impl Hooks<WriteState> for SourceHooks<'_> {
fn taken(&self, st: &mut WriteState, after: &mut After) {
if self.src.catch_up(st.epoch_mut()) {
st.discard(after);
}
}
fn serve(&self, st: &mut WriteState, after: &mut After) {
#[cfg(readiness)]
if let Some(fd) = self.src.serving_fd(self.token) {
st.serve(fd, self.src.kind, true, after);
}
#[cfg(not(readiness))]
let _ = (st, after);
}
fn deferred(&self, st: &mut WriteState, bits: u8, after: &mut After) {
if bits & write_work::UNSTARVE != 0 {
st.unstarve(after);
}
if bits & write_work::ABANDON != 0 {
st.abandon_lent(after);
#[cfg(completion)]
if let Some(user_data) = st.withdrawal() {
self.src.cancel_op(user_data);
}
}
#[cfg(completion)]
{
if bits & write_work::DONE != 0
&& let Some(done) = self.src.write_done.take()
{
st.op_done(done, self.src.serving_fd(None), after);
}
if bits & (write_work::DONE | write_work::SUBMIT) != 0 {
self.src
.with_route(|io| st.submit_wanted(io, self.src.kind, after));
}
}
#[cfg(port)]
if bits & write_work::LOOK != 0 {
st.wake_all(after);
}
}
fn released(&self, saw_requests: bool, after: &mut After) {
if saw_requests {
self.src.requests_after_write(after);
}
}
fn releasing(&self, st: &mut WriteState) {
self.src.unsent.store(st.has_unsent(), Ordering::Release);
}
}
impl Source {
pub(crate) fn new(fd: Raw, kind: Kind) -> Arc<Self> {
Arc::new(Self {
fd: AtomicRaw::new(fd),
retired_fd: AtomicRaw::new(NO_FD),
epoch: AtomicU32::new(0),
retired_epoch: AtomicU32::new(0),
kind,
read: Side::new(ReadState::default()),
write: Side::new(WriteState::default()),
reg: Registration {
state: AtomicU8::new(UNREGISTERED),
token: AtomicU64::new(NO_TOKEN),
shard: UnsafeCell::new(None),
#[cfg(completion)]
ops: AtomicBool::new(false),
#[cfg(ring)]
slot: AtomicU32::new(u32::MAX),
home: core::sync::atomic::AtomicUsize::new(0),
#[cfg(port)]
awaits_idle: AtomicBool::new(false),
},
requests: AtomicU8::new(0),
ask: Ask::default(),
unsent: AtomicBool::new(false),
#[cfg(completion)]
read_done: ops::Mailbox::default(),
#[cfg(completion)]
write_done: ops::Mailbox::default(),
#[cfg(ring)]
read_arrivals: kovan_queue::seg_queue::SegQueue::new(),
#[cfg(completion)]
ops_out: core::sync::atomic::AtomicU8::new(0),
#[cfg(port)]
port: Default::default(),
})
}
pub(crate) fn kind(&self) -> Kind {
self.kind
}
fn hooks(self: &Arc<Self>) -> SourceHooks<'_> {
SourceHooks {
src: self,
#[cfg(readiness)]
token: None,
}
}
pub(crate) fn raw_fd(&self) -> io::Result<Raw> {
self.fd()
}
fn fd(&self) -> io::Result<Raw> {
match self.fd.load(Ordering::Acquire) {
NO_FD => Err(io::Error::new(
io::ErrorKind::NotConnected,
"the handle's IO object is not in place",
)),
fd => Ok(fd),
}
}
fn with_io<R>(self: &Arc<Self>, f: impl FnOnce(Io<'_>) -> R) -> io::Result<R> {
let fd = self.fd()?;
#[cfg(completion)]
if self.kind != Kind::File {
return context::current(|current| {
if !self.reg.ops.load(Ordering::Acquire) {
#[cfg(readiness)]
{
let rings = current
.map_or_else(crate::rt::runtime::service_rings, |s| s.ring().is_some());
if !rings || self.reg.state.load(Ordering::Acquire) == REGISTERED {
return Ok(f(Io::Fd(fd)));
}
}
self.ensure_registered()?;
}
Ok(f(Io::Ops(
self.route(current).unwrap_or(route::Route::NotYet),
)))
});
}
#[cfg(readiness)]
return Ok(f(Io::Fd(fd)));
#[cfg(not(readiness))]
{
let _ = (fd, f);
Err(io::Error::new(
io::ErrorKind::Unsupported,
"a regular file is read and written through its handle",
))
}
}
#[cfg(not(completion))]
fn ops_idle(&self) -> bool {
true
}
fn serving_fd(&self, token: Option<u64>) -> Option<Raw> {
if token.is_some_and(|t| self.reg.token.load(Ordering::Acquire) != t) {
return None;
}
match self.fd.load(Ordering::Acquire) {
NO_FD => None,
fd => Some(fd),
}
}
fn catch_up(&self, seen: &mut u32) -> bool {
let now = self.epoch.load(Ordering::Acquire);
if *seen == now {
return false;
}
*seen = now;
true
}
fn settle(self: &Arc<Self>, mut after: After) {
#[cfg(ring)]
if let Some(home) = after.kick.take() {
home.post_io(crate::rt::shard::IoOp::RingKick(Arc::clone(self)));
}
if after.starved {
crate::buf::with_pool(|pool| {
if let Some(pool) = pool {
pool.wait_for_return(Arc::clone(self).into());
}
});
}
after.run();
}
fn finish_poll<R>(
self: &Arc<Self>,
after: After,
poll: Poll<io::Result<R>>,
) -> Poll<io::Result<R>> {
let watch = after.register || poll.is_pending();
self.settle(after);
if watch && let Err(err) = self.ensure_registered() {
return Poll::Ready(Err(err));
}
poll
}
fn ensure_registered(self: &Arc<Self>) -> io::Result<()> {
if self.reg.state.load(Ordering::Acquire) != UNREGISTERED {
return Ok(());
}
if self
.reg
.state
.compare_exchange(
UNREGISTERED,
REGISTERING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
return Ok(());
}
match context::current(|current| current.map(|shard| shard.register_source(self))) {
Some(result) => result,
None => {
crate::rt::runtime::service_shard().post_io(IoOp::Register(Arc::clone(self)));
Ok(())
}
}
}
pub(crate) fn registration_abandoned(&self) {
self.reg.state.store(UNREGISTERED, Ordering::Release);
}
#[cfg_attr(not(ring), expect(unused_variables))]
fn detach(self: &Arc<Self>, read: &ReadState, write: &WriteState) {
#[cfg(ring)]
if self.ring_attach_under_way() {
self.abandon_ring_attach();
return;
}
if self.reg.state.load(Ordering::Acquire) != REGISTERED {
return;
}
let shard = unsafe { (*self.reg.shard.get()).take() };
let token = self.reg.token.swap(NO_TOKEN, Ordering::AcqRel);
self.reg.home.store(0, Ordering::Release);
self.reg.state.store(UNREGISTERED, Ordering::Release);
if let Some(shard) = shard {
#[cfg(ring)]
if self.reg.ops.swap(false, Ordering::AcqRel) {
let slot = self.reg.slot.swap(u32::MAX, Ordering::AcqRel);
self.detach_ring(&shard, slot, [read.out(), write.out()]);
shard.release_source(token);
return;
}
#[cfg(port)]
if self.reg.ops.swap(false, Ordering::AcqRel) {
self.detach_port();
shard.release_source(token);
return;
}
#[cfg(readiness)]
let fd = self.fd.load(Ordering::Acquire);
#[cfg(readiness)]
if fd != NO_FD
&& let Some(registrar) = shard.registrar()
{
let _ = registrar.delete(fd, self.kind == Kind::Tcp);
}
shard.release_source(token);
}
}
#[cfg(unix)]
pub(crate) fn duplicate(&self) -> Option<std::os::fd::OwnedFd> {
super::sys::duplicate(self.fd().ok()?).ok()
}
pub(crate) fn revive(&self, fd: Raw) {
if self.fd.load(Ordering::Acquire) != NO_FD {
return;
}
if self.retired_fd.load(Ordering::Acquire) != fd {
let retired = self.retired_epoch.load(Ordering::Acquire);
let _ = self.epoch.compare_exchange(
retired,
retired.wrapping_add(1),
Ordering::AcqRel,
Ordering::Acquire,
);
}
let _ = self
.fd
.compare_exchange(NO_FD, fd, Ordering::AcqRel, Ordering::Acquire);
}
pub(crate) fn is_retired(&self) -> bool {
self.fd.load(Ordering::Acquire) == NO_FD
}
pub(crate) fn registered_with(&self, home: usize, token: u64) -> bool {
self.reg.token.load(Ordering::Acquire) == token
&& self.reg.home.load(Ordering::Acquire) == home
}
pub(crate) fn has_unsent(&self) -> bool {
self.unsent.load(Ordering::Acquire)
}
#[cfg(readiness)]
pub(crate) fn on_event(self: &Arc<Self>, token: u64, readiness: Readiness) {
let mut after = After::default();
let hooks = SourceHooks {
src: self,
token: Some(token),
};
if readiness.readable {
let unsure = if readiness.urgent || readiness.hangup {
read_work::UNSURE
} else {
0
};
self.read.drive(&mut after, &hooks, unsure);
}
if readiness.writable {
self.write.drive(&mut after, &hooks, 0);
}
self.settle(after);
}
}
#[cfg(test)]
impl Source {
pub(crate) fn registration(&self) -> (usize, u64) {
(
self.reg.home.load(Ordering::SeqCst),
self.reg.token.load(Ordering::SeqCst),
)
}
pub(crate) fn read_report(self: &Arc<Self>) -> String {
let (word, deferred) = self.read.words();
let held = match self.read.try_hold() {
Some(mut held) => {
let line = held.get().report();
let mut after = After::default();
held.release(&mut after, &self.hooks());
after.run();
line
}
None => "held".into(),
};
format!("guard {word:#x}, deferred {deferred:#x}: {held}")
}
pub(crate) fn write_report(self: &Arc<Self>) -> String {
let (word, deferred) = self.write.words();
let held = match self.write.try_hold() {
Some(mut held) => {
let line = held.get().report();
let mut after = After::default();
held.release(&mut after, &self.hooks());
after.run();
line
}
None => "held".into(),
};
#[cfg(completion)]
let ops = format!(
", ops registered {}, ops out {}",
self.reg.ops.load(Ordering::SeqCst),
self.ops_out.load(Ordering::SeqCst)
);
#[cfg(not(completion))]
let ops = String::new();
format!(
"guard {word:#x}, deferred {deferred:#x}: {held}; registration {}, retired {}{ops}",
self.reg.state.load(Ordering::SeqCst),
self.is_retired(),
)
}
fn has_read_ahead(self: &Arc<Self>) -> bool {
let mut after = After::default();
let hooks = self.hooks();
self.read.try_hold().is_some_and(|mut held| {
let found = held.get().has_result();
held.release(&mut after, &hooks);
found
})
}
fn has_write_behind(self: &Arc<Self>) -> bool {
let mut after = After::default();
let hooks = self.hooks();
self.write.try_hold().is_some_and(|mut held| {
let found = held.get().has_behind();
held.release(&mut after, &hooks);
found
})
}
}
impl Drop for Source {
fn drop(&mut self) {
let mut after = After::default();
self.read.get_mut().discard(&mut after);
self.write.get_mut().discard(&mut after);
after.run();
}
}
impl Wake for Source {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
let mut after = After::default();
let hooks = self.hooks();
self.read.defer(read_work::UNSTARVE, &mut after, &hooks);
self.write.defer(write_work::UNSTARVE, &mut after, &hooks);
self.settle(after);
}
}
#[cfg(test)]
mod tests;