feat(log): add bounded diagnostic transport
This commit is contained in:
1 parent
632bf9f01b
commit
0a75860841
46 files changed
+2758
-60
No files matched your search
+108
-8
@@ -8,7 +8,11 @@
|
||||
/// Delivery importance used for bounded admission and selection.
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum FrameClass {
|
||||
/// Liveness or status traffic that best-effort samples cannot displace.
|
||||
/// Heartbeat and current safety/health status. A pending vital frame may
|
||||
/// displace any pending non-vital frame; active DMA storage is untouched.
|
||||
Vital,
|
||||
/// Fault transitions and authoritative replies that best-effort samples
|
||||
/// cannot displace.
|
||||
Critical,
|
||||
/// Replaceable sample traffic where newest pending data is most useful.
|
||||
BestEffort,
|
||||
@@ -30,6 +34,10 @@ pub enum EnqueueResult {
|
||||
Enqueued,
|
||||
/// The frame displaced one pending best-effort frame.
|
||||
ReplacedBestEffort,
|
||||
/// A vital frame displaced one pending critical frame.
|
||||
ReplacedCritical,
|
||||
/// Vital admission failed because every pending slot was vital.
|
||||
VitalQueueFull,
|
||||
/// Critical admission failed because no best-effort pending frame existed.
|
||||
CriticalQueueFull,
|
||||
/// Best-effort admission failed because no replaceable pending frame existed.
|
||||
@@ -75,16 +83,30 @@ impl<Frame: Copy, const N: usize> TransmitQueue<Frame, N> {
|
||||
return EnqueueResult::Enqueued;
|
||||
}
|
||||
|
||||
if let Some(slot) = self
|
||||
let best_effort = self
|
||||
.pending
|
||||
.iter_mut()
|
||||
.find(|slot| slot.is_some_and(|pending| pending.class == FrameClass::BestEffort))
|
||||
{
|
||||
*slot = Some(frame);
|
||||
return EnqueueResult::ReplacedBestEffort;
|
||||
.iter()
|
||||
.position(|slot| slot.is_some_and(|pending| pending.class == FrameClass::BestEffort));
|
||||
let critical = self
|
||||
.pending
|
||||
.iter()
|
||||
.position(|slot| slot.is_some_and(|pending| pending.class == FrameClass::Critical));
|
||||
let replacement = match frame.class {
|
||||
FrameClass::Vital => best_effort
|
||||
.map(|index| (index, EnqueueResult::ReplacedBestEffort))
|
||||
.or_else(|| critical.map(|index| (index, EnqueueResult::ReplacedCritical))),
|
||||
FrameClass::Critical => {
|
||||
best_effort.map(|index| (index, EnqueueResult::ReplacedBestEffort))
|
||||
}
|
||||
FrameClass::BestEffort => None,
|
||||
};
|
||||
if let Some((index, result)) = replacement {
|
||||
self.pending[index] = Some(frame);
|
||||
return result;
|
||||
}
|
||||
|
||||
match frame.class {
|
||||
FrameClass::Vital => EnqueueResult::VitalQueueFull,
|
||||
FrameClass::Critical => EnqueueResult::CriticalQueueFull,
|
||||
FrameClass::BestEffort => EnqueueResult::BestEffortDropped,
|
||||
}
|
||||
@@ -131,11 +153,17 @@ impl<Frame: Copy, const N: usize> TransmitQueue<Frame, N> {
|
||||
}
|
||||
|
||||
fn take_next(&mut self) -> Option<PendingFrame<Frame>> {
|
||||
let vital = self
|
||||
.pending
|
||||
.iter()
|
||||
.position(|slot| slot.is_some_and(|pending| pending.class == FrameClass::Vital));
|
||||
let critical = self
|
||||
.pending
|
||||
.iter()
|
||||
.position(|slot| slot.is_some_and(|pending| pending.class == FrameClass::Critical));
|
||||
let index = critical.or_else(|| self.pending.iter().position(Option::is_some));
|
||||
let index = vital
|
||||
.or(critical)
|
||||
.or_else(|| self.pending.iter().position(Option::is_some));
|
||||
index.and_then(|index| self.pending[index].take())
|
||||
}
|
||||
}
|
||||
@@ -231,6 +259,78 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vital_status_displaces_pending_critical_and_wins_selection() {
|
||||
let mut queue = TransmitQueue::<u8, 2>::new();
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::Critical,
|
||||
frame: 1,
|
||||
});
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::BestEffort,
|
||||
frame: 2,
|
||||
});
|
||||
assert_eq!(
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::Vital,
|
||||
frame: 3,
|
||||
}),
|
||||
EnqueueResult::ReplacedBestEffort
|
||||
);
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::Vital,
|
||||
frame: 4,
|
||||
});
|
||||
assert_eq!(
|
||||
queue.start_with(|frame| {
|
||||
assert_eq!(*frame, 4);
|
||||
Ok::<_, ()>(())
|
||||
}),
|
||||
Ok(StartOutcome::Started)
|
||||
);
|
||||
queue.complete();
|
||||
assert_eq!(
|
||||
queue.start_with(|frame| {
|
||||
assert_eq!(*frame, 3);
|
||||
Ok::<_, ()>(())
|
||||
}),
|
||||
Ok(StartOutcome::Started)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admitted_vital_metadata_cannot_be_displaced_by_diagnostics() {
|
||||
let mut queue = TransmitQueue::<u8, 1>::new();
|
||||
assert_eq!(
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::Vital,
|
||||
frame: 11,
|
||||
}),
|
||||
EnqueueResult::Enqueued
|
||||
);
|
||||
assert_eq!(
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::Critical,
|
||||
frame: 12,
|
||||
}),
|
||||
EnqueueResult::CriticalQueueFull
|
||||
);
|
||||
assert_eq!(
|
||||
queue.enqueue(PendingFrame {
|
||||
class: FrameClass::BestEffort,
|
||||
frame: 13,
|
||||
}),
|
||||
EnqueueResult::BestEffortDropped
|
||||
);
|
||||
assert_eq!(
|
||||
queue.start_with(|frame| {
|
||||
assert_eq!(*frame, 11);
|
||||
Ok::<_, ()>(())
|
||||
}),
|
||||
Ok(StartOutcome::Started)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_start_without_claiming_active_ownership() {
|
||||
let mut queue = TransmitQueue::<u8, 1>::new();
|
||||
|
||||
Reference in new issue
Block a user