duende_mlock/
status.rs

1//! Status types for memory locking results.
2
3use std::fmt;
4
5/// Result of a memory locking operation.
6///
7/// This type captures both successful and unsuccessful (but non-fatal) outcomes.
8/// Fatal errors are returned as [`Err(MlockError)`](crate::MlockError).
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum MlockStatus {
11    /// Memory was successfully locked.
12    ///
13    /// Contains the number of bytes locked at the time of the call.
14    /// Note: This may increase as more memory is allocated (with `MCL_FUTURE`).
15    Locked {
16        /// Bytes locked at time of call (from /proc/self/status `VmLck`).
17        bytes_locked: usize,
18    },
19
20    /// Memory locking failed but was not required.
21    ///
22    /// Only returned when `MlockConfig::required(false)` is set.
23    /// The daemon should continue with a warning.
24    Failed {
25        /// The errno that caused the failure.
26        errno: i32,
27    },
28
29    /// Memory locking is not supported on this platform.
30    ///
31    /// Returned on non-Unix platforms (Windows, WASM, etc.).
32    Unsupported,
33}
34
35impl MlockStatus {
36    /// Check if memory is currently locked.
37    ///
38    /// Returns `true` for [`MlockStatus::Locked`], `false` otherwise.
39    #[must_use]
40    pub const fn is_locked(&self) -> bool {
41        matches!(self, Self::Locked { .. })
42    }
43
44    /// Check if locking failed.
45    ///
46    /// Returns `true` for [`MlockStatus::Failed`], `false` otherwise.
47    #[must_use]
48    pub const fn is_failed(&self) -> bool {
49        matches!(self, Self::Failed { .. })
50    }
51
52    /// Check if mlock is unsupported on this platform.
53    ///
54    /// Returns `true` for [`MlockStatus::Unsupported`], `false` otherwise.
55    #[must_use]
56    pub const fn is_unsupported(&self) -> bool {
57        matches!(self, Self::Unsupported)
58    }
59
60    /// Get the number of bytes locked.
61    ///
62    /// Returns the bytes locked for [`MlockStatus::Locked`], `0` otherwise.
63    #[must_use]
64    pub const fn bytes_locked(&self) -> usize {
65        match self {
66            Self::Locked { bytes_locked } => *bytes_locked,
67            Self::Failed { .. } | Self::Unsupported => 0,
68        }
69    }
70
71    /// Get the failure errno, if any.
72    ///
73    /// Returns `Some(errno)` for [`MlockStatus::Failed`], `None` otherwise.
74    #[must_use]
75    pub const fn failure_errno(&self) -> Option<i32> {
76        match self {
77            Self::Failed { errno } => Some(*errno),
78            Self::Locked { .. } | Self::Unsupported => None,
79        }
80    }
81}
82
83impl fmt::Display for MlockStatus {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::Locked { bytes_locked } => {
87                if *bytes_locked >= 1024 * 1024 {
88                    write!(f, "locked ({} MB)", bytes_locked / (1024 * 1024))
89                } else if *bytes_locked >= 1024 {
90                    write!(f, "locked ({} KB)", bytes_locked / 1024)
91                } else {
92                    write!(f, "locked ({bytes_locked} bytes)")
93                }
94            }
95            Self::Failed { errno } => {
96                write!(f, "failed (errno={errno})")
97            }
98            Self::Unsupported => {
99                write!(f, "unsupported platform")
100            }
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_locked_status() {
111        let status = MlockStatus::Locked {
112            bytes_locked: 4096,
113        };
114        assert!(status.is_locked());
115        assert!(!status.is_failed());
116        assert!(!status.is_unsupported());
117        assert_eq!(status.bytes_locked(), 4096);
118        assert_eq!(status.failure_errno(), None);
119    }
120
121    #[test]
122    fn test_failed_status() {
123        let status = MlockStatus::Failed { errno: 1 };
124        assert!(!status.is_locked());
125        assert!(status.is_failed());
126        assert!(!status.is_unsupported());
127        assert_eq!(status.bytes_locked(), 0);
128        assert_eq!(status.failure_errno(), Some(1));
129    }
130
131    #[test]
132    fn test_unsupported_status() {
133        let status = MlockStatus::Unsupported;
134        assert!(!status.is_locked());
135        assert!(!status.is_failed());
136        assert!(status.is_unsupported());
137        assert_eq!(status.bytes_locked(), 0);
138        assert_eq!(status.failure_errno(), None);
139    }
140
141    #[test]
142    fn test_display_locked_bytes() {
143        let status = MlockStatus::Locked { bytes_locked: 512 };
144        assert_eq!(format!("{status}"), "locked (512 bytes)");
145    }
146
147    #[test]
148    fn test_display_locked_kb() {
149        let status = MlockStatus::Locked {
150            bytes_locked: 4096,
151        };
152        assert_eq!(format!("{status}"), "locked (4 KB)");
153    }
154
155    #[test]
156    fn test_display_locked_mb() {
157        let status = MlockStatus::Locked {
158            bytes_locked: 10 * 1024 * 1024,
159        };
160        assert_eq!(format!("{status}"), "locked (10 MB)");
161    }
162
163    #[test]
164    fn test_display_failed() {
165        let status = MlockStatus::Failed { errno: 12 };
166        assert_eq!(format!("{status}"), "failed (errno=12)");
167    }
168
169    #[test]
170    fn test_display_unsupported() {
171        let status = MlockStatus::Unsupported;
172        assert_eq!(format!("{status}"), "unsupported platform");
173    }
174}