Skip to main content

running_process_platform_internal/platform/
resources.rs

1//! What this host says about running out of things.
2//!
3//! A caller that fails to open a socket or write a file needs to know *which*
4//! wall it hit -- descriptors, or space -- because the two call for different
5//! responses: shed load and retry, or stop and free something. The number the
6//! host returns to say so is different on each of them, and in the Windows
7//! case there are three numbers rather than one.
8//!
9//! So the classification lives here and the response stays with the caller.
10//! Nothing in these signatures names an errno or a Win32 code.
11
12pub use crate::{
13    resources_fd_exhaustion_error as fd_exhaustion_error,
14    resources_inode_capacity as inode_capacity,
15    resources_signals_fd_exhaustion as signals_fd_exhaustion,
16    resources_signals_storage_exhaustion as signals_storage_exhaustion,
17    resources_storage_exhaustion_error as storage_exhaustion_error,
18};
19
20/// How many inodes a filesystem has, and how many are still available.
21///
22/// Only filesystems with a fixed inode table have this to report, so callers
23/// receive it as an `Option` and must decide what an absent answer means.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct InodeCapacity {
26    /// Total inodes on the filesystem.
27    pub total: u64,
28    /// Inodes available to unprivileged users.
29    pub free: u64,
30}
31
32#[cfg(test)]
33mod tests {
34    use std::io;
35
36    use super::*;
37
38    /// The classifiers must agree with the errors this host reports, or a
39    /// caller keying on them retries the wrong wall -- or neither.
40    #[test]
41    fn this_hosts_exhaustion_errors_are_recognised() {
42        assert!(
43            signals_fd_exhaustion(&fd_exhaustion_error()),
44            "a descriptor-exhaustion error must be recognised as one"
45        );
46        assert!(
47            signals_storage_exhaustion(&storage_exhaustion_error()),
48            "a storage-exhaustion error must be recognised as one"
49        );
50    }
51
52    /// The two conditions are not the same wall, and must not be confused for
53    /// one another.
54    #[test]
55    fn the_two_exhaustion_conditions_stay_distinct() {
56        assert!(!signals_storage_exhaustion(&fd_exhaustion_error()));
57        assert!(!signals_fd_exhaustion(&storage_exhaustion_error()));
58    }
59
60    /// An error carrying no OS code cannot be either condition; guessing from
61    /// its text would make an unrelated failure look like exhaustion.
62    #[test]
63    fn an_error_without_an_os_code_signals_nothing() {
64        let synthetic = io::Error::other("no operating system said this");
65        assert!(!signals_fd_exhaustion(&synthetic));
66        assert!(!signals_storage_exhaustion(&synthetic));
67    }
68
69    /// An ordinary failure is not exhaustion.
70    #[test]
71    fn an_unrelated_os_error_signals_nothing() {
72        let denied = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
73        assert!(!signals_fd_exhaustion(&denied));
74        assert!(!signals_storage_exhaustion(&denied));
75    }
76
77    /// Whatever this host reports for a directory that exists, it is either a
78    /// coherent capacity or an explicit "not applicable" -- never an error.
79    #[test]
80    fn probing_an_existing_directory_answers_coherently() {
81        let probed = inode_capacity(&std::env::temp_dir()).expect("probing temp dir must succeed");
82        if let Some(capacity) = probed {
83            assert!(capacity.total > 0, "a reported table is never empty");
84            assert!(
85                capacity.free <= capacity.total,
86                "free inodes cannot exceed the table"
87            );
88        }
89    }
90}