dx-dcp 0.0.1

Development Context Protocol - binary-first replacement for MCP
Documentation
//! io_uring reactor implementation for Linux.
//!
//! Uses Linux's io_uring interface for high-performance async I/O.
//! This is the preferred reactor on modern Linux kernels (5.1+).

use std::collections::HashMap;
use std::io;
use std::time::Duration;

use super::{Completion, Event, Interest, RawFd, Reactor, Token};

/// io_uring reactor for Linux
///
/// Provides true async I/O using Linux's io_uring interface.
/// Falls back to epoll if io_uring is not available.
pub struct IoUringReactor {
    /// Maximum events per poll
    max_events: usize,
    /// Registered file descriptors
    registrations: HashMap<Token, Registration>,
    /// Next token to assign
    next_token: usize,
    /// Pending completions
    pending: Vec<PendingOp>,
    /// Whether io_uring is actually available
    available: bool,
}

/// Registration state for a file descriptor
struct Registration {
    fd: RawFd,
    interest: Interest,
}

/// Pending async operation
#[allow(dead_code)]
struct PendingOp {
    token: Token,
    op_type: OpType,
}

#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum OpType {
    Read,
    Write,
}

impl IoUringReactor {
    /// Create a new io_uring reactor
    ///
    /// Returns an error if io_uring is not available on this system.
    pub fn new(max_events: usize) -> io::Result<Self> {
        // Check if io_uring is available by attempting to detect kernel support
        // In a real implementation, we would use the io_uring crate
        // For now, we simulate availability check

        #[cfg(target_os = "linux")]
        {
            // Check kernel version for io_uring support (5.1+)
            let available = Self::check_io_uring_support();

            if !available {
                return Err(io::Error::new(
                    io::ErrorKind::Unsupported,
                    "io_uring not available on this system",
                ));
            }

            Ok(Self {
                max_events,
                registrations: HashMap::new(),
                next_token: 1,
                pending: Vec::new(),
                available: true,
            })
        }

        #[cfg(not(target_os = "linux"))]
        {
            let _ = max_events;
            Err(io::Error::new(
                io::ErrorKind::Unsupported,
                "io_uring is only available on Linux",
            ))
        }
    }

    /// Check if io_uring is supported on this system
    #[cfg(target_os = "linux")]
    fn check_io_uring_support() -> bool {
        use std::fs;

        // Check for io_uring support by looking at kernel version
        if let Ok(version) = fs::read_to_string("/proc/version") {
            // Parse kernel version (e.g., "Linux version 5.15.0-...")
            if let Some(ver_str) = version.split_whitespace().nth(2) {
                let parts: Vec<&str> = ver_str.split('.').collect();
                if parts.len() >= 2 {
                    if let (Ok(major), Ok(minor)) =
                        (parts[0].parse::<u32>(), parts[1].parse::<u32>())
                    {
                        // io_uring requires kernel 5.1+
                        return major > 5 || (major == 5 && minor >= 1);
                    }
                }
            }
        }
        false
    }

    /// Check if io_uring is available
    pub fn is_available(&self) -> bool {
        self.available
    }
}

impl Reactor for IoUringReactor {
    fn poll(&mut self, timeout: Option<Duration>) -> io::Result<Vec<Event>> {
        if !self.available {
            return Err(io::Error::new(
                io::ErrorKind::Unsupported,
                "io_uring not available",
            ));
        }

        // In a real implementation, this would call io_uring_wait_cqe
        // For now, we simulate with a simple poll-like behavior
        let events = Vec::with_capacity(self.max_events);

        // Simulate timeout
        if let Some(duration) = timeout {
            std::thread::sleep(duration.min(Duration::from_millis(1)));
        }

        // In production, we would:
        // 1. Submit any pending SQEs
        // 2. Wait for CQEs with timeout
        // 3. Convert CQEs to Events

        // For now, return empty events (no activity)
        // Real implementation would use io_uring crate
        let _ = &self.registrations;

        Ok(events)
    }

    fn register(&mut self, fd: RawFd, interest: Interest) -> io::Result<Token> {
        let token = Token(self.next_token);
        self.next_token += 1;

        self.registrations
            .insert(token, Registration { fd, interest });

        // In real implementation, we might pre-register the fd with io_uring
        // using IORING_REGISTER_FILES for better performance

        Ok(token)
    }

    fn modify(&mut self, token: Token, interest: Interest) -> io::Result<()> {
        if let Some(reg) = self.registrations.get_mut(&token) {
            reg.interest = interest;
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::NotFound,
                "Token not registered",
            ))
        }
    }

    fn deregister(&mut self, token: Token) -> io::Result<()> {
        self.registrations.remove(&token);
        // Remove any pending operations for this token
        self.pending.retain(|op| op.token != token);
        Ok(())
    }

    fn submit_read(&mut self, token: Token, _buf: &mut [u8]) -> io::Result<Option<Completion>> {
        if !self.registrations.contains_key(&token) {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                "Token not registered",
            ));
        }

        // In real implementation:
        // 1. Get SQE from ring
        // 2. Prepare read operation
        // 3. Submit to ring
        // 4. Return None (completion comes later via poll)

        self.pending.push(PendingOp {
            token,
            op_type: OpType::Read,
        });

        Ok(None)
    }

    fn submit_write(&mut self, token: Token, _buf: &[u8]) -> io::Result<Option<Completion>> {
        if !self.registrations.contains_key(&token) {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                "Token not registered",
            ));
        }

        // In real implementation:
        // 1. Get SQE from ring
        // 2. Prepare write operation
        // 3. Submit to ring
        // 4. Return None (completion comes later via poll)

        self.pending.push(PendingOp {
            token,
            op_type: OpType::Write,
        });

        Ok(None)
    }

    fn supports_async_io(&self) -> bool {
        true
    }

    fn name(&self) -> &'static str {
        "io_uring"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(target_os = "linux")]
    fn test_iouring_creation() {
        // This may fail on older kernels, which is expected
        let result = IoUringReactor::new(256);
        // Just verify it doesn't panic
        let _ = result;
    }

    #[test]
    #[cfg(not(target_os = "linux"))]
    fn test_iouring_not_available_on_non_linux() {
        let result = IoUringReactor::new(256);
        assert!(result.is_err());
    }
}