use super::TargetRoom;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArenaState {
pub page_size: u64,
pub has_outdated: bool,
pub full: bool,
pub shape: ArenaShape,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArenaShape {
pub current_pages: u64,
pub outdated_pools: usize,
pub outdated_pages: u64,
pub outdated_guarded: usize,
}
#[derive(Debug, Clone)]
pub struct RelocationTrigger {
capacity: Option<u64>,
stalled: Option<ArenaShape>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocationNeed {
Nothing,
ArenaFull,
UnderPressure {
capacity: u64,
page_size: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocationReason {
Explicit,
MemoryPressure,
ArenaFull,
Capture,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MemoryPressure {
Low,
High,
}
impl RelocationTrigger {
pub fn new(capacity: Option<u64>) -> Self {
Self {
capacity,
stalled: None,
}
}
pub fn need(&self, arena: &ArenaState) -> RelocationNeed {
if !arena.has_outdated || self.stalled == Some(arena.shape) {
return RelocationNeed::Nothing;
}
if arena.full {
return RelocationNeed::ArenaFull;
}
match self.capacity {
Some(capacity) => RelocationNeed::UnderPressure {
capacity,
page_size: arena.page_size,
},
None => RelocationNeed::Nothing,
}
}
pub fn moved(&mut self) {
self.stalled = None;
}
pub fn stalled(&mut self, arena: &ArenaState) {
self.stalled = Some(arena.shape);
}
}
impl RelocationNeed {
pub fn reason(self, allocated: impl FnOnce() -> u64) -> Option<RelocationReason> {
match self {
RelocationNeed::Nothing => None,
RelocationNeed::ArenaFull => Some(RelocationReason::ArenaFull),
RelocationNeed::UnderPressure {
capacity,
page_size,
} => match MemoryPressure::new(allocated(), capacity, page_size) {
MemoryPressure::High => Some(RelocationReason::MemoryPressure),
MemoryPressure::Low => None,
},
}
}
}
impl RelocationReason {
pub fn room(self) -> TargetRoom {
match self {
RelocationReason::Explicit | RelocationReason::MemoryPressure => TargetRoom::Held,
RelocationReason::ArenaFull | RelocationReason::Capture => TargetRoom::MayAllocate,
}
}
}
impl MemoryPressure {
fn new(allocated: u64, capacity: u64, page_size: u64) -> Self {
match allocated + page_size > capacity.saturating_sub(page_size) {
true => MemoryPressure::High,
false => MemoryPressure::Low,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const MIB: u64 = 1024 * 1024;
fn arena(page_size: u64) -> ArenaState {
ArenaState {
page_size,
has_outdated: true,
full: false,
shape: ArenaShape {
current_pages: 1,
outdated_pools: 1,
outdated_pages: 1,
outdated_guarded: 0,
},
}
}
#[test]
fn pressure_is_high_once_the_next_page_leaves_less_than_another() {
let need = RelocationTrigger::new(Some(24 * MIB)).need(&arena(4 * MIB));
assert_eq!(need.reason(|| 10 * MIB), None);
assert_eq!(
need.reason(|| 17 * MIB),
Some(RelocationReason::MemoryPressure)
);
}
#[test]
fn nothing_outdated_needs_nothing_and_sums_nothing() {
let state = ArenaState {
has_outdated: false,
..arena(4 * MIB)
};
let need = RelocationTrigger::new(Some(MIB)).need(&state);
assert_eq!(need, RelocationNeed::Nothing);
assert_eq!(need.reason(|| panic!("the bytes are never summed")), None);
}
#[test]
fn a_full_arena_relocates_whatever_the_device_holds() {
let state = ArenaState {
full: true,
..arena(4 * MIB)
};
assert_eq!(
RelocationTrigger::new(None).need(&state).reason(|| 0),
Some(RelocationReason::ArenaFull)
);
}
#[test]
fn a_device_that_reports_no_capacity_is_not_judged() {
assert_eq!(
RelocationTrigger::new(None).need(&arena(MIB)),
RelocationNeed::Nothing
);
}
#[test]
fn a_relocation_that_moved_nothing_waits_for_the_pools_to_change() {
let mut trigger = RelocationTrigger::new(Some(24 * MIB));
let state = arena(4 * MIB);
trigger.stalled(&state);
assert_eq!(trigger.need(&state), RelocationNeed::Nothing);
let grown = ArenaState {
shape: ArenaShape {
current_pages: 2,
..state.shape
},
..state
};
assert_ne!(trigger.need(&grown), RelocationNeed::Nothing);
}
}