use thiserror::Error;
#[non_exhaustive]
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControllerError {
#[error("controller not configured")]
NotConfigured,
#[error("submission queue full")]
Full,
#[error("controller resource limit reached: {resource} (limit: {limit})")]
#[non_exhaustive]
ResourceLimit {
resource: &'static str,
limit: usize,
},
#[error(
"failed to start {component} worker {worker} during controller submission: {kind:?} (raw OS error: {raw_os_error:?})"
)]
#[non_exhaustive]
ThreadStartFailed {
component: &'static str,
worker: usize,
kind: std::io::ErrorKind,
raw_os_error: Option<i32>,
},
#[error("controller channel closed")]
Closed,
#[error("controller already started")]
AlreadyStarted,
}
impl ControllerError {
#[must_use]
pub fn as_label(&self) -> &'static str {
match self {
ControllerError::ThreadStartFailed { .. } => "controller_thread_start_failed",
ControllerError::ResourceLimit { .. } => "controller_resource_limit",
ControllerError::AlreadyStarted => "controller_already_started",
ControllerError::NotConfigured => "controller_not_configured",
ControllerError::Closed => "controller_closed",
ControllerError::Full => "controller_full",
}
}
}
#[cfg(test)]
mod tests {
use super::ControllerError;
use crate::core::deferred_drop::OWNERSHIP_RESOURCE;
#[test]
fn public_error_labels_cover_every_public_variant() {
for (error, expected) in [
(ControllerError::NotConfigured, "controller_not_configured"),
(ControllerError::Full, "controller_full"),
(
ControllerError::ResourceLimit {
resource: OWNERSHIP_RESOURCE,
limit: 1024,
},
"controller_resource_limit",
),
(
ControllerError::ThreadStartFailed {
component: "destructor_isolation",
worker: 2,
kind: std::io::ErrorKind::PermissionDenied,
raw_os_error: Some(13),
},
"controller_thread_start_failed",
),
(ControllerError::Closed, "controller_closed"),
(
ControllerError::AlreadyStarted,
"controller_already_started",
),
] {
assert_eq!(error.as_label(), expected);
}
}
#[test]
fn resource_limit_preserves_resource_and_limit() {
let error = ControllerError::ResourceLimit {
resource: OWNERSHIP_RESOURCE,
limit: 1024,
};
assert_eq!(
error.to_string(),
"controller resource limit reached: owned_user_lifetimes (limit: 1024)"
);
let ControllerError::ResourceLimit {
resource, limit, ..
} = error
else {
unreachable!("the constructed variant is a resource limit")
};
assert_eq!(resource, OWNERSHIP_RESOURCE);
assert_eq!(limit, 1024);
}
#[test]
fn thread_start_failure_preserves_copyable_io_details() {
let error = ControllerError::ThreadStartFailed {
component: "destructor_isolation",
worker: 2,
kind: std::io::ErrorKind::PermissionDenied,
raw_os_error: Some(13),
};
assert_eq!(
error.to_string(),
"failed to start destructor_isolation worker 2 during controller submission: PermissionDenied (raw OS error: Some(13))"
);
let ControllerError::ThreadStartFailed {
component,
worker,
kind,
raw_os_error,
..
} = error
else {
unreachable!("the constructed variant is a thread-start failure")
};
assert_eq!(component, "destructor_isolation");
assert_eq!(worker, 2);
assert_eq!(kind, std::io::ErrorKind::PermissionDenied);
assert_eq!(raw_os_error, Some(13));
}
}