crossio-core 0.1.0

Core abstractions for the crossio async I/O backend
Documentation
//! Public reactor abstraction that delegates to a backend implementation.
use crate::{
    backend::Backend,
    config::BackendConfig,
    error::CrossioError,
    event::Events,
    registration::{Registration, Source},
};
use std::time::Duration;

/// High-level wrapper around a backend-specific poller.
pub struct Reactor<B: Backend> {
    backend: B,
    events: Events,
}

impl<B: Backend> Reactor<B> {
    /// Builds a new reactor with the default configuration.
    pub fn new() -> Result<Self, CrossioError> {
        Self::with_config(BackendConfig::default())
    }

    /// Builds a new reactor using customized configuration.
    pub fn with_config(config: BackendConfig) -> Result<Self, CrossioError> {
        let capacity = config.event_batch_size();
        Ok(Self {
            backend: B::new(config)?,
            events: Events::with_capacity(capacity),
        })
    }

    /// Registers a source with the provided token and interest mask.
    pub fn register<S: Source<B>>(
        &self,
        source: &S,
        registration: Registration,
    ) -> Result<(), CrossioError> {
        if registration.interest().is_empty() {
            return Err(CrossioError::InvalidConfig("interest mask cannot be empty"));
        }

        self.backend.register(
            source.raw_source(),
            registration.token(),
            registration.interest(),
        )
    }

    /// Updates the interest associated with a previously registered source.
    pub fn reregister<S: Source<B>>(
        &self,
        source: &S,
        registration: Registration,
    ) -> Result<(), CrossioError> {
        self.backend.reregister(
            source.raw_source(),
            registration.token(),
            registration.interest(),
        )
    }

    /// Removes a source from the reactor.
    pub fn deregister<S: Source<B>>(&self, source: &S) -> Result<(), CrossioError> {
        self.backend.deregister(source.raw_source())
    }

    /// Polls the backend for readiness changes, filling the internal events buffer.
    pub fn poll(&mut self, timeout: Option<Duration>) -> Result<&Events, CrossioError> {
        self.events.clear();
        self.backend.poll(&mut self.events, timeout)?;
        Ok(&self.events)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Event, Events, Interest, Token};

    #[derive(Clone, Default)]
    struct DummyBackend;

    impl Backend for DummyBackend {
        type RawSource = usize;

        fn new(_: BackendConfig) -> Result<Self, CrossioError> {
            Ok(Self)
        }

        fn register(
            &self,
            _: Self::RawSource,
            _: Token,
            _: Interest,
        ) -> Result<(), CrossioError> {
            Ok(())
        }

        fn reregister(
            &self,
            _: Self::RawSource,
            _: Token,
            _: Interest,
        ) -> Result<(), CrossioError> {
            Ok(())
        }

        fn deregister(&self, _: Self::RawSource) -> Result<(), CrossioError> {
            Ok(())
        }

        fn poll(
            &self,
            events: &mut Events,
            _: Option<Duration>,
        ) -> Result<usize, CrossioError> {
            events.push(Event::new(Token::from(0usize), Interest::READABLE));
            Ok(events.len())
        }
    }

    struct DummySource(usize);

    impl Source<DummyBackend> for DummySource {
        fn raw_source(&self) -> <DummyBackend as Backend>::RawSource {
            self.0
        }
    }

    #[test]
    fn register_rejects_empty_interest() {
        let reactor = Reactor::<DummyBackend>::new().expect("dummy backend should initialize");
        let source = DummySource(1);
        let registration = Registration::new(Token::from_usize(1), Interest::empty());

        match reactor.register(&source, registration) {
            Err(CrossioError::InvalidConfig(_)) => {}
            other => panic!("expected invalid config error, got {other:?}"),
        }
    }

    #[test]
    fn poll_returns_events_from_backend() {
        let mut reactor = Reactor::<DummyBackend>::new().unwrap();
        let events = reactor.poll(Some(Duration::from_secs(0))).unwrap();
        assert_eq!(events.len(), 1);
        let event = events.iter().next().unwrap();
        assert_eq!(event.token(), Token::from_usize(0));
        assert!(event.readiness().contains(Interest::READABLE));
    }
}