safer_ring/
runtime.rs

1//! Runtime detection and fallback system for safer-ring.
2//!
3//! This module provides runtime detection of io_uring availability and automatic
4//! fallback to alternative implementations when io_uring is not available. This is
5//! particularly important in cloud environments like Docker, Kubernetes (GKE, EKS),
6//! and serverless platforms where io_uring is often disabled by default.
7//!
8//! # Architecture
9//!
10//! The runtime uses a backend abstraction that can switch between:
11//! - **IoUringBackend**: Full io_uring implementation (Linux 5.1+)
12//! - **EpollBackend**: Traditional epoll-based fallback (all Linux)
13//! - **StubBackend**: No-op implementation for non-Linux platforms
14//!
15//! # Example
16//!
17//! ```rust,no_run
18//! use safer_ring::runtime::{Runtime, Backend};
19//!
20//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! // Automatically detect best available backend
22//! let runtime = Runtime::auto_detect()?;
23//!
24//! match runtime.backend() {
25//!     Backend::IoUring => println!("Using high-performance io_uring"),
26//!     Backend::Epoll => println!("Using epoll fallback"),
27//!     Backend::Stub => println!("Using stub implementation"),
28//! }
29//!
30//! // Check if we're in a restricted environment
31//! if runtime.is_cloud_environment() {
32//!     println!("Detected cloud environment, see docs for optimization tips");
33//! }
34//! # Ok(())
35//! # }
36//! ```
37
38use std::fs;
39use std::io;
40use std::path::Path;
41
42use crate::error::{Result, SaferRingError};
43
44/// Available runtime backends for IO operations.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Backend {
47    /// High-performance io_uring backend (Linux 5.1+)
48    IoUring,
49    /// Traditional epoll-based backend (all Linux)
50    Epoll,
51    /// Stub implementation for non-Linux platforms
52    Stub,
53}
54
55impl Backend {
56    /// Get a human-readable description of the backend.
57    pub fn description(&self) -> &'static str {
58        match self {
59            Backend::IoUring => "High-performance io_uring backend",
60            Backend::Epoll => "Traditional epoll-based backend",
61            Backend::Stub => "Stub implementation for non-Linux platforms",
62        }
63    }
64
65    /// Check if this backend supports advanced features.
66    pub fn supports_advanced_features(&self) -> bool {
67        matches!(self, Backend::IoUring)
68    }
69
70    /// Get expected performance multiplier compared to epoll baseline.
71    pub fn performance_multiplier(&self) -> f32 {
72        match self {
73            Backend::IoUring => 3.0, // 3x performance improvement
74            Backend::Epoll => 1.0,   // Baseline
75            Backend::Stub => 0.1,    // Minimal performance
76        }
77    }
78}
79
80/// Runtime environment with automatic backend detection and fallback.
81///
82/// The runtime automatically detects the best available backend and provides
83/// information about the current environment for optimization guidance.
84#[derive(Debug)]
85pub struct Runtime {
86    backend: Backend,
87    environment: EnvironmentInfo,
88}
89
90impl Runtime {
91    /// Create a new runtime with automatic backend detection.
92    ///
93    /// This method probes the system for io_uring availability and selects
94    /// the best available backend. If io_uring is not available, it falls
95    /// back to epoll (on Linux) or stub implementation (on other platforms).
96    ///
97    /// # Returns
98    ///
99    /// A runtime configured with the optimal backend for the current environment.
100    ///
101    /// # Example
102    ///
103    /// ```rust,no_run
104    /// use safer_ring::runtime::Runtime;
105    ///
106    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107    /// let runtime = Runtime::auto_detect()?;
108    /// println!("Using backend: {}", runtime.backend().description());
109    /// # Ok(())
110    /// # }
111    /// ```
112    pub fn auto_detect() -> Result<Self> {
113        let environment = EnvironmentInfo::detect();
114        let backend = Self::select_best_backend(&environment)?;
115
116        // Log warnings for restricted environments
117        if environment.is_cloud_environment() && backend != Backend::IoUring {
118            eprintln!("WARNING: io_uring not available in cloud environment");
119            eprintln!("Performance may be degraded. See documentation for configuration tips:");
120            eprintln!("- Docker: Add --cap-add SYS_ADMIN or use --privileged");
121            eprintln!("- Kubernetes: Set privileged: true or configure seccomp/apparmor");
122            eprintln!("- Cloud Run/Lambda: Consider using Cloud Functions with custom runtimes");
123        }
124
125        Ok(Self {
126            backend,
127            environment,
128        })
129    }
130
131    /// Create a runtime with a specific backend.
132    ///
133    /// This bypasses automatic detection and forces the use of a specific backend.
134    /// Use this when you want to test fallback behavior or have specific requirements.
135    ///
136    /// # Arguments
137    ///
138    /// * `backend` - The backend to use
139    ///
140    /// # Example
141    ///
142    /// ```rust,no_run
143    /// use safer_ring::runtime::{Runtime, Backend};
144    ///
145    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
146    /// // Force epoll backend for testing
147    /// let runtime = Runtime::with_backend(Backend::Epoll)?;
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub fn with_backend(backend: Backend) -> Result<Self> {
152        let environment = EnvironmentInfo::detect();
153
154        // Validate that the requested backend is available
155        match backend {
156            Backend::IoUring => {
157                if !Self::is_io_uring_available() {
158                    return Err(SaferRingError::Io(io::Error::new(
159                        io::ErrorKind::Unsupported,
160                        "io_uring backend requested but not available on this system",
161                    )));
162                }
163            }
164            Backend::Epoll => {
165                #[cfg(not(target_os = "linux"))]
166                {
167                    return Err(SaferRingError::Io(io::Error::new(
168                        io::ErrorKind::Unsupported,
169                        "epoll backend only available on Linux",
170                    )));
171                }
172            }
173            Backend::Stub => {
174                // Stub backend is always available
175            }
176        }
177
178        Ok(Self {
179            backend,
180            environment,
181        })
182    }
183
184    /// Get the active backend.
185    pub fn backend(&self) -> Backend {
186        self.backend
187    }
188
189    /// Get environment information.
190    pub fn environment(&self) -> &EnvironmentInfo {
191        &self.environment
192    }
193
194    /// Check if running in a cloud environment.
195    pub fn is_cloud_environment(&self) -> bool {
196        self.environment.is_cloud_environment()
197    }
198
199    /// Get performance guidance for the current environment.
200    pub fn performance_guidance(&self) -> Vec<&'static str> {
201        let mut guidance = Vec::new();
202
203        match self.backend {
204            Backend::IoUring => {
205                guidance.push("✓ Using high-performance io_uring backend");
206                if self.environment.container_runtime.is_some() {
207                    guidance
208                        .push("Consider tuning container security settings for better performance");
209                }
210            }
211            Backend::Epoll => {
212                guidance.push("⚠ Using epoll fallback - performance may be limited");
213                if self.is_cloud_environment() {
214                    guidance
215                        .push("Check cloud platform documentation for enabling io_uring support");
216                }
217            }
218            Backend::Stub => {
219                guidance.push("⚠ Using stub implementation - limited functionality");
220                guidance.push("Consider running on Linux for better performance");
221            }
222        }
223
224        if let Some(container) = &self.environment.container_runtime {
225            match container.as_str() {
226                "docker" => {
227                    guidance.push("Docker detected: Add --cap-add SYS_ADMIN for io_uring support");
228                }
229                "containerd" | "cri-o" => {
230                    guidance.push(
231                        "Kubernetes detected: Configure privileged pods or custom seccomp profiles",
232                    );
233                }
234                _ => {}
235            }
236        }
237
238        guidance
239    }
240
241    /// Select the best available backend for the current environment.
242    fn select_best_backend(_environment: &EnvironmentInfo) -> Result<Backend> {
243        // Check platform first
244        #[cfg(not(target_os = "linux"))]
245        {
246            Ok(Backend::Stub)
247        }
248
249        #[cfg(target_os = "linux")]
250        {
251            // Try io_uring first
252            if Self::is_io_uring_available() {
253                // Additional checks for restricted environments
254                if _environment.is_cloud_environment() {
255                    // In cloud environments, io_uring might be restricted
256                    if let Some(restriction) = Self::check_io_uring_restrictions() {
257                        eprintln!("io_uring restricted: {restriction}");
258                        return Ok(Backend::Epoll);
259                    }
260                }
261                return Ok(Backend::IoUring);
262            }
263
264            // Fall back to epoll
265            Ok(Backend::Epoll)
266        }
267    }
268
269    /// Check if io_uring is available on the system.
270    #[cfg(target_os = "linux")]
271    fn is_io_uring_available() -> bool {
272        // Try to create a minimal io_uring instance
273        io_uring::IoUring::new(1).is_ok()
274    }
275
276    /// Check if io_uring is available (always false on non-Linux).
277    #[cfg(not(target_os = "linux"))]
278    fn is_io_uring_available() -> bool {
279        false
280    }
281
282    /// Check for io_uring restrictions in the current environment.
283    #[cfg(target_os = "linux")]
284    #[allow(dead_code)]
285    fn check_io_uring_restrictions() -> Option<String> {
286        // Check seccomp restrictions
287        if let Ok(status) = fs::read_to_string("/proc/self/status") {
288            if status.contains("Seccomp:") && !status.contains("Seccomp:\t0") {
289                return Some("seccomp profile may restrict io_uring system calls".to_string());
290            }
291        }
292
293        // Check AppArmor restrictions
294        if Path::new("/proc/self/attr/current").exists() {
295            if let Ok(apparmor) = fs::read_to_string("/proc/self/attr/current") {
296                if !apparmor.trim().is_empty() && apparmor.trim() != "unconfined" {
297                    return Some("AppArmor profile may restrict io_uring".to_string());
298                }
299            }
300        }
301
302        // Check for container indicators that might restrict io_uring
303        if Path::new("/.dockerenv").exists() {
304            return Some("Docker environment detected - io_uring may be restricted".to_string());
305        }
306
307        None
308    }
309
310    /// Check for io_uring restrictions (no-op on non-Linux).
311    #[cfg(not(target_os = "linux"))]
312    #[allow(dead_code)]
313    fn check_io_uring_restrictions() -> Option<String> {
314        None
315    }
316}
317
318/// Information about the runtime environment.
319///
320/// Collects information about the system environment to help guide
321/// performance optimization and provide helpful warnings.
322#[derive(Debug)]
323pub struct EnvironmentInfo {
324    /// Detected container runtime (docker, containerd, etc.)
325    pub container_runtime: Option<String>,
326    /// Whether running in Kubernetes
327    pub kubernetes: bool,
328    /// Whether running in a cloud serverless environment
329    pub serverless: bool,
330    /// Kernel version for Linux systems
331    pub kernel_version: Option<String>,
332    /// Available CPU count
333    pub cpu_count: usize,
334}
335
336impl EnvironmentInfo {
337    /// Detect current environment information.
338    pub fn detect() -> Self {
339        Self {
340            container_runtime: Self::detect_container_runtime(),
341            kubernetes: Self::detect_kubernetes(),
342            serverless: Self::detect_serverless(),
343            kernel_version: Self::detect_kernel_version(),
344            cpu_count: Self::detect_cpu_count(),
345        }
346    }
347
348    /// Check if running in any kind of cloud environment.
349    pub fn is_cloud_environment(&self) -> bool {
350        self.container_runtime.is_some() || self.kubernetes || self.serverless
351    }
352
353    /// Detect container runtime.
354    fn detect_container_runtime() -> Option<String> {
355        // Check for Docker
356        if Path::new("/.dockerenv").exists() {
357            return Some("docker".to_string());
358        }
359
360        // Check for other container indicators
361        if let Ok(cgroup) = fs::read_to_string("/proc/1/cgroup") {
362            if cgroup.contains("docker") {
363                return Some("docker".to_string());
364            }
365            if cgroup.contains("containerd") {
366                return Some("containerd".to_string());
367            }
368            if cgroup.contains("cri-o") {
369                return Some("cri-o".to_string());
370            }
371        }
372
373        None
374    }
375
376    /// Detect Kubernetes environment.
377    fn detect_kubernetes() -> bool {
378        std::env::var("KUBERNETES_SERVICE_HOST").is_ok()
379            || Path::new("/var/run/secrets/kubernetes.io").exists()
380    }
381
382    /// Detect serverless environment.
383    fn detect_serverless() -> bool {
384        // AWS Lambda
385        std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() ||
386        // Google Cloud Functions
387        std::env::var("FUNCTION_NAME").is_ok() ||
388        // Azure Functions
389        std::env::var("AZURE_FUNCTIONS_ENVIRONMENT").is_ok() ||
390        // Cloud Run
391        std::env::var("K_SERVICE").is_ok()
392    }
393
394    /// Detect kernel version on Linux.
395    #[cfg(target_os = "linux")]
396    fn detect_kernel_version() -> Option<String> {
397        fs::read_to_string("/proc/version")
398            .ok()
399            .and_then(|v| v.split_whitespace().nth(2).map(|s| s.to_string()))
400    }
401
402    /// Detect kernel version (none on non-Linux).
403    #[cfg(not(target_os = "linux"))]
404    fn detect_kernel_version() -> Option<String> {
405        None
406    }
407
408    /// Detect CPU count.
409    fn detect_cpu_count() -> usize {
410        std::thread::available_parallelism()
411            .map(|p| p.get())
412            .unwrap_or(1)
413    }
414}
415
416/// Check if io_uring is available in the current environment.
417///
418/// This is a convenience function that creates a minimal runtime to test availability.
419///
420/// # Example
421///
422/// ```rust,no_run
423/// use safer_ring::runtime::is_io_uring_available;
424///
425/// if is_io_uring_available() {
426///     println!("io_uring is available!");
427/// } else {
428///     println!("io_uring is not available, will use fallback");
429/// }
430/// ```
431pub fn is_io_uring_available() -> bool {
432    Runtime::is_io_uring_available()
433}
434
435/// Get environment information for the current system.
436///
437/// # Example
438///
439/// ```rust,no_run
440/// use safer_ring::runtime::get_environment_info;
441///
442/// let env = get_environment_info();
443/// if env.is_cloud_environment() {
444///     println!("Running in cloud environment");
445/// }
446/// println!("CPU count: {}", env.cpu_count);
447/// ```
448pub fn get_environment_info() -> EnvironmentInfo {
449    EnvironmentInfo::detect()
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455
456    #[test]
457    fn test_backend_properties() {
458        assert_eq!(
459            Backend::IoUring.description(),
460            "High-performance io_uring backend"
461        );
462        assert_eq!(
463            Backend::Epoll.description(),
464            "Traditional epoll-based backend"
465        );
466        assert_eq!(
467            Backend::Stub.description(),
468            "Stub implementation for non-Linux platforms"
469        );
470
471        assert!(Backend::IoUring.supports_advanced_features());
472        assert!(!Backend::Epoll.supports_advanced_features());
473        assert!(!Backend::Stub.supports_advanced_features());
474
475        assert_eq!(Backend::IoUring.performance_multiplier(), 3.0);
476        assert_eq!(Backend::Epoll.performance_multiplier(), 1.0);
477        assert_eq!(Backend::Stub.performance_multiplier(), 0.1);
478    }
479
480    #[test]
481    fn test_runtime_auto_detect() {
482        let runtime = Runtime::auto_detect().unwrap();
483
484        // Should have detected some backend
485        match runtime.backend() {
486            Backend::IoUring | Backend::Epoll | Backend::Stub => {}
487        }
488
489        // Environment should be detected
490        let env = runtime.environment();
491        assert!(env.cpu_count > 0);
492    }
493
494    #[test]
495    fn test_environment_detection() {
496        let env = EnvironmentInfo::detect();
497
498        // Should detect CPU count
499        assert!(env.cpu_count > 0);
500
501        // Cloud environment detection should not panic
502        let _ = env.is_cloud_environment();
503    }
504
505    #[test]
506    fn test_performance_guidance() {
507        let runtime = Runtime::auto_detect().unwrap();
508        let guidance = runtime.performance_guidance();
509
510        // Should provide at least one guidance point
511        assert!(!guidance.is_empty());
512
513        // Each guidance should be non-empty
514        for guide in guidance {
515            assert!(!guide.is_empty());
516        }
517    }
518
519    #[cfg(target_os = "linux")]
520    #[test]
521    fn test_stub_backend_on_non_linux_request() {
522        // On Linux, requesting stub should work
523        let runtime = Runtime::with_backend(Backend::Stub).unwrap();
524        assert_eq!(runtime.backend(), Backend::Stub);
525    }
526
527    #[cfg(not(target_os = "linux"))]
528    #[test]
529    fn test_epoll_backend_fails_on_non_linux() {
530        // On non-Linux, requesting epoll should fail
531        let result = Runtime::with_backend(Backend::Epoll);
532        assert!(result.is_err());
533    }
534
535    #[test]
536    fn test_is_io_uring_available() {
537        // Should not panic regardless of availability
538        let _ = is_io_uring_available();
539    }
540
541    #[test]
542    fn test_get_environment_info() {
543        let env = get_environment_info();
544        assert!(env.cpu_count > 0);
545    }
546}