#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ThreadPriority {
Background = 0,
Lowest = 1,
BelowNormal = 2,
#[default]
Normal = 3,
AboveNormal = 4,
Highest = 5,
TimeCritical = 6,
}
impl std::fmt::Display for ThreadPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThreadPriority::Background => write!(f, "Background"),
ThreadPriority::Lowest => write!(f, "Lowest"),
ThreadPriority::BelowNormal => write!(f, "BelowNormal"),
ThreadPriority::Normal => write!(f, "Normal"),
ThreadPriority::AboveNormal => write!(f, "AboveNormal"),
ThreadPriority::Highest => write!(f, "Highest"),
ThreadPriority::TimeCritical => write!(f, "TimeCritical"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Grant {
Direct,
Brokered,
Realtime,
}
impl std::fmt::Display for Grant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Grant::Direct => write!(f, "Direct"),
Grant::Brokered => write!(f, "Brokered"),
Grant::Realtime => write!(f, "Realtime"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FallbackReason {
NoBroker,
BrokerTimedOut,
BrokerRefused,
Clamped,
}
impl std::fmt::Display for FallbackReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FallbackReason::NoBroker => write!(f, "NoBroker"),
FallbackReason::BrokerTimedOut => write!(f, "BrokerTimedOut"),
FallbackReason::BrokerRefused => write!(f, "BrokerRefused"),
FallbackReason::Clamped => write!(f, "Clamped"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum BrokerError {
AccessDenied,
LimitsExceeded,
InvalidArgs,
Failed,
Other,
}
impl BrokerError {
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn from_dbus_name(name: &str) -> BrokerError {
match name {
"org.freedesktop.DBus.Error.AccessDenied" => BrokerError::AccessDenied,
"org.freedesktop.DBus.Error.LimitsExceeded" => BrokerError::LimitsExceeded,
"org.freedesktop.DBus.Error.InvalidArgs" => BrokerError::InvalidArgs,
"org.freedesktop.DBus.Error.Failed" => BrokerError::Failed,
_ => BrokerError::Other,
}
}
}
impl std::fmt::Display for BrokerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BrokerError::AccessDenied => write!(f, "AccessDenied"),
BrokerError::LimitsExceeded => write!(f, "LimitsExceeded"),
BrokerError::InvalidArgs => write!(f, "InvalidArgs"),
BrokerError::Failed => write!(f, "Failed"),
BrokerError::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MechanismPolicy {
Nice,
SchedRr,
SchedOther,
Qos,
WinPriority,
}
impl std::fmt::Display for MechanismPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MechanismPolicy::Nice => write!(f, "nice"),
MechanismPolicy::SchedRr => write!(f, "SCHED_RR"),
MechanismPolicy::SchedOther => write!(f, "SCHED_OTHER"),
MechanismPolicy::Qos => write!(f, "QoS"),
MechanismPolicy::WinPriority => write!(f, "THREAD_PRIORITY"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i8)]
pub enum QosClass {
Background = 0,
Utility = 1,
Default = 2,
UserInitiated = 3,
UserInteractive = 4,
}
impl QosClass {
#[must_use]
pub fn from_value(value: i8) -> Option<QosClass> {
match value {
0 => Some(QosClass::Background),
1 => Some(QosClass::Utility),
2 => Some(QosClass::Default),
3 => Some(QosClass::UserInitiated),
4 => Some(QosClass::UserInteractive),
_ => None,
}
}
}
impl std::fmt::Display for QosClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QosClass::Background => write!(f, "Background"),
QosClass::Utility => write!(f, "Utility"),
QosClass::Default => write!(f, "Default"),
QosClass::UserInitiated => write!(f, "UserInitiated"),
QosClass::UserInteractive => write!(f, "UserInteractive"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mechanism {
pub policy: MechanismPolicy,
pub value: i8,
}
impl std::fmt::Display for Mechanism {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.policy {
MechanismPolicy::Qos => match QosClass::from_value(self.value) {
Some(class) => write!(f, "QoS {class}"),
None => write!(f, "QoS {}", self.value),
},
other => write!(f, "{} {}", other, self.value),
}
}
}
#[must_use = "a priority request can silently fall back (no privilege or no broker); \
inspect the result -- degraded()/effective/reason -- instead of discarding it, \
or the downgrade goes unnoticed"]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AppliedPriority {
requested: ThreadPriority,
effective: ThreadPriority,
grant: Grant,
reason: Option<FallbackReason>,
mechanism: Mechanism,
broker_error: Option<BrokerError>,
}
impl AppliedPriority {
#[must_use]
pub fn from_parts(
requested: ThreadPriority,
effective: ThreadPriority,
grant: Grant,
reason: Option<FallbackReason>,
mechanism: Mechanism,
broker_error: Option<BrokerError>,
) -> Option<Self> {
if broker_error.is_some() && reason != Some(FallbackReason::BrokerRefused) {
return None;
}
Some(Self {
requested,
effective,
grant,
reason,
mechanism,
broker_error,
})
}
pub(crate) fn new(
requested: ThreadPriority,
effective: ThreadPriority,
grant: Grant,
mechanism: Mechanism,
) -> Self {
Self::from_parts(requested, effective, grant, None, mechanism, None)
.expect("clean priority outcome is valid")
}
#[must_use]
pub fn requested(&self) -> ThreadPriority {
self.requested
}
#[must_use]
pub fn effective(&self) -> ThreadPriority {
self.effective
}
#[must_use]
pub fn grant(&self) -> Grant {
self.grant
}
#[must_use]
pub fn reason(&self) -> Option<FallbackReason> {
self.reason
}
#[must_use]
pub fn mechanism(&self) -> Mechanism {
self.mechanism
}
#[must_use]
pub fn broker_error(&self) -> Option<BrokerError> {
self.broker_error
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn with_reason(mut self, reason: FallbackReason) -> Self {
self.reason = Some(reason);
self
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn with_broker_error(mut self, broker_error: BrokerError) -> Self {
self.broker_error = Some(broker_error);
self
}
#[must_use]
pub fn degraded(&self) -> bool {
self.reason.is_some()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for AppliedPriority {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct Parts {
requested: ThreadPriority,
effective: ThreadPriority,
grant: Grant,
reason: Option<FallbackReason>,
mechanism: Mechanism,
broker_error: Option<BrokerError>,
}
let parts = Parts::deserialize(deserializer)?;
AppliedPriority::from_parts(
parts.requested,
parts.effective,
parts.grant,
parts.reason,
parts.mechanism,
parts.broker_error,
)
.ok_or_else(|| serde::de::Error::custom("broker_error requires reason BrokerRefused"))
}
}
impl std::fmt::Display for AppliedPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.requested != self.effective {
write!(f, "{} -> {}", self.requested, self.effective)?;
if self.grant != Grant::Direct || self.reason.is_some() {
write!(f, " [")?;
let mut sep = "";
if self.grant != Grant::Direct {
write!(f, "{}", self.grant)?;
sep = ", ";
}
if let Some(reason) = self.reason {
write!(f, "{sep}{reason:?}")?;
}
write!(f, "]")?;
}
} else {
write!(f, "{}", self.effective)?;
if self.grant != Grant::Direct || self.reason.is_some() {
write!(f, " [")?;
let mut sep = "";
if self.grant != Grant::Direct {
write!(f, "{}", self.grant)?;
sep = ", ";
}
if let Some(reason) = self.reason {
write!(f, "{sep}{reason:?}")?;
}
write!(f, "]")?;
}
}
if let Some(broker_error) = self.broker_error {
write!(f, " ({broker_error})")?;
}
write!(f, " {}", self.mechanism)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thread_priority_ordinals_are_stable() {
assert_eq!(ThreadPriority::Background as u8, 0);
assert_eq!(ThreadPriority::Lowest as u8, 1);
assert_eq!(ThreadPriority::BelowNormal as u8, 2);
assert_eq!(ThreadPriority::Normal as u8, 3);
assert_eq!(ThreadPriority::AboveNormal as u8, 4);
assert_eq!(ThreadPriority::Highest as u8, 5);
assert_eq!(ThreadPriority::TimeCritical as u8, 6);
}
#[test]
fn broker_error_maps_known_dbus_names() {
assert_eq!(
BrokerError::from_dbus_name("org.freedesktop.DBus.Error.AccessDenied"),
BrokerError::AccessDenied
);
assert_eq!(
BrokerError::from_dbus_name("org.freedesktop.DBus.Error.LimitsExceeded"),
BrokerError::LimitsExceeded
);
assert_eq!(
BrokerError::from_dbus_name("org.freedesktop.DBus.Error.InvalidArgs"),
BrokerError::InvalidArgs
);
assert_eq!(
BrokerError::from_dbus_name("org.freedesktop.DBus.Error.Failed"),
BrokerError::Failed
);
}
#[test]
fn broker_error_unmapped_name_is_other() {
assert_eq!(
BrokerError::from_dbus_name("org.freedesktop.RealtimeKit1.Error.Whatever"),
BrokerError::Other
);
assert_eq!(BrokerError::from_dbus_name(""), BrokerError::Other);
}
#[test]
fn mechanism_display_renders_per_policy() {
assert_eq!(
Mechanism {
policy: MechanismPolicy::Nice,
value: -15
}
.to_string(),
"nice -15"
);
assert_eq!(
Mechanism {
policy: MechanismPolicy::SchedRr,
value: 47
}
.to_string(),
"SCHED_RR 47"
);
assert_eq!(
Mechanism {
policy: MechanismPolicy::Qos,
value: QosClass::UserInteractive as i8
}
.to_string(),
"QoS UserInteractive"
);
assert_eq!(
Mechanism {
policy: MechanismPolicy::WinPriority,
value: 2
}
.to_string(),
"THREAD_PRIORITY 2"
);
}
#[test]
fn applied_priority_display_appends_mechanism() {
let clean = AppliedPriority::new(
ThreadPriority::Normal,
ThreadPriority::Normal,
Grant::Direct,
Mechanism {
policy: MechanismPolicy::Nice,
value: 0,
},
);
assert_eq!(clean.to_string(), "Normal nice 0");
let brokered = AppliedPriority::new(
ThreadPriority::Highest,
ThreadPriority::Highest,
Grant::Brokered,
Mechanism {
policy: MechanismPolicy::Nice,
value: -10,
},
);
assert_eq!(brokered.to_string(), "Highest [Brokered] nice -10");
let clamped = AppliedPriority::new(
ThreadPriority::TimeCritical,
ThreadPriority::TimeCritical,
Grant::Brokered,
Mechanism {
policy: MechanismPolicy::Nice,
value: -15,
},
)
.with_reason(FallbackReason::Clamped);
assert_eq!(
clamped.to_string(),
"TimeCritical [Brokered, Clamped] nice -15"
);
}
}