use {
crate::{event::VotorEvent, timer_manager::stats::TimerManagerStats},
crossbeam_channel::Sender,
solana_clock::{NUM_CONSECUTIVE_LEADER_SLOTS, Slot},
solana_runtime::leader_schedule_utils::last_of_consecutive_leader_slots,
std::{
cmp::Reverse,
collections::{BinaryHeap, HashMap, VecDeque},
time::{Duration, Instant},
},
};
const MAX_TIMEOUT_SECS: f64 = 3600.0;
fn calculate_timeout_multiplier(slot: Slot, standstill_slot: Option<Slot>) -> f64 {
match standstill_slot {
None => 1.0,
Some(standstill_slot) => {
let slots_since_standstill = slot.saturating_sub(standstill_slot);
let leader_windows = slots_since_standstill / NUM_CONSECUTIVE_LEADER_SLOTS;
1.05_f64.powi(leader_windows as i32)
}
}
}
enum TimerState {
WaitDeltaTimeout {
window: VecDeque<Slot>,
timeout: Instant,
scaled_delta_block: Duration,
},
WaitDeltaBlock {
window: VecDeque<Slot>,
timeout: Instant,
scaled_delta_block: Duration,
},
Done,
}
impl TimerState {
fn new(
slot: Slot,
delta_timeout: Duration,
delta_block: Duration,
now: Instant,
timeout_multiplier: f64,
) -> (Self, Instant) {
let window = (slot..=last_of_consecutive_leader_slots(slot)).collect::<VecDeque<_>>();
assert!(!window.is_empty());
let scaled_delta_timeout = Duration::from_secs_f64(
(delta_timeout.as_secs_f64() * timeout_multiplier).min(MAX_TIMEOUT_SECS),
);
let scaled_delta_block = Duration::from_secs_f64(
(delta_block.as_secs_f64() * timeout_multiplier).min(MAX_TIMEOUT_SECS),
);
let timeout = now.checked_add(scaled_delta_timeout).unwrap();
(
Self::WaitDeltaTimeout {
window,
timeout,
scaled_delta_block,
},
timeout,
)
}
fn progress(&mut self, now: Instant) -> Option<VotorEvent> {
match self {
Self::WaitDeltaTimeout {
window,
timeout,
scaled_delta_block,
} => {
assert!(!window.is_empty());
if &now < timeout {
return None;
}
let slot = *window.front().unwrap();
let new_timeout = timeout.checked_add(*scaled_delta_block).unwrap();
*self = Self::WaitDeltaBlock {
window: window.to_owned(),
timeout: new_timeout,
scaled_delta_block: *scaled_delta_block,
};
Some(VotorEvent::TimeoutCrashedLeader(slot))
}
Self::WaitDeltaBlock {
window,
timeout,
scaled_delta_block,
} => {
assert!(!window.is_empty());
if &now < timeout {
return None;
}
let ret = Some(VotorEvent::Timeout(window.pop_front().unwrap()));
match window.front() {
None => *self = Self::Done,
Some(_next_slot) => {
*timeout = timeout.checked_add(*scaled_delta_block).unwrap();
}
}
ret
}
Self::Done => None,
}
}
fn next_fire(&self) -> Option<Instant> {
match self {
Self::WaitDeltaTimeout { timeout, .. } | Self::WaitDeltaBlock { timeout, .. } => {
Some(*timeout)
}
Self::Done => None,
}
}
}
pub(super) struct Timers {
delta_timeout: Duration,
timers: HashMap<Slot, TimerState>,
heap: BinaryHeap<Reverse<(Instant, Slot)>>,
event_sender: Sender<VotorEvent>,
stats: TimerManagerStats,
}
impl Timers {
pub(super) fn new(delta_timeout: Duration, event_sender: Sender<VotorEvent>) -> Self {
Self {
delta_timeout,
timers: HashMap::new(),
heap: BinaryHeap::new(),
event_sender,
stats: TimerManagerStats::new(),
}
}
pub(super) fn set_timeouts(
&mut self,
slot: Slot,
now: Instant,
standstill_slot: Option<Slot>,
delta_block: Duration,
) {
assert_eq!(self.heap.len(), self.timers.len());
let timeout_multiplier = calculate_timeout_multiplier(slot, standstill_slot);
let (timer, next_fire) = TimerState::new(
slot,
self.delta_timeout,
delta_block,
now,
timeout_multiplier,
);
let mut new_timer_inserted = false;
self.timers.entry(slot).or_insert_with(|| {
self.heap.push(Reverse((next_fire, slot)));
new_timer_inserted = true;
timer
});
self.stats
.incr_timeout_count_with_heap_size(self.heap.len(), new_timer_inserted);
}
pub(super) fn progress(&mut self, now: Instant) -> Option<Instant> {
assert_eq!(self.heap.len(), self.timers.len());
let mut ret_timeout = None;
loop {
assert_eq!(self.heap.len(), self.timers.len());
match self.heap.pop() {
None => break,
Some(Reverse((next_fire, slot))) => {
if now < next_fire {
ret_timeout =
Some(ret_timeout.map_or(next_fire, |r| std::cmp::min(r, next_fire)));
self.heap.push(Reverse((next_fire, slot)));
break;
}
let mut timer = self.timers.remove(&slot).unwrap();
if let Some(event) = timer.progress(now) {
self.event_sender.send(event).unwrap();
}
if let Some(next_fire) = timer.next_fire() {
self.heap.push(Reverse((next_fire, slot)));
assert!(self.timers.insert(slot, timer).is_none());
ret_timeout =
Some(ret_timeout.map_or(next_fire, |r| std::cmp::min(r, next_fire)));
}
}
}
}
ret_timeout
}
#[cfg(test)]
pub(super) fn stats(&self) -> TimerManagerStats {
self.stats.clone()
}
#[cfg(test)]
pub(super) fn is_timeout_set(&self, slot: Slot) -> bool {
self.timers.contains_key(&slot)
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::common::DELTA_TIMEOUT, crossbeam_channel::unbounded,
solana_clock::DEFAULT_MS_PER_SLOT,
};
#[test]
fn timer_state_machine() {
let one_micro = Duration::from_micros(1);
let now = Instant::now();
let slot = 0;
let (mut timer_state, next_fire) = TimerState::new(slot, one_micro, one_micro, now, 1.0);
assert!(matches!(
timer_state.progress(next_fire).unwrap(),
VotorEvent::TimeoutCrashedLeader(0)
));
assert!(matches!(
timer_state
.progress(timer_state.next_fire().unwrap())
.unwrap(),
VotorEvent::Timeout(0)
));
assert!(matches!(
timer_state
.progress(timer_state.next_fire().unwrap())
.unwrap(),
VotorEvent::Timeout(1)
));
assert!(matches!(
timer_state
.progress(timer_state.next_fire().unwrap())
.unwrap(),
VotorEvent::Timeout(2)
));
assert!(matches!(
timer_state
.progress(timer_state.next_fire().unwrap())
.unwrap(),
VotorEvent::Timeout(3)
));
assert!(timer_state.next_fire().is_none());
}
#[test]
fn timers_progress() {
let one_micro = Duration::from_micros(1);
let mut now = Instant::now();
let (sender, receiver) = unbounded();
let mut timers = Timers::new(one_micro, sender);
assert!(timers.progress(now).is_none());
assert!(receiver.try_recv().unwrap_err().is_empty());
timers.set_timeouts(0, now, None, Duration::from_millis(DEFAULT_MS_PER_SLOT));
while timers.progress(now).is_some() {
now = now.checked_add(one_micro).unwrap();
}
let mut events = receiver.try_iter().collect::<Vec<_>>();
assert!(matches!(
events.remove(0),
VotorEvent::TimeoutCrashedLeader(0)
));
assert!(matches!(events.remove(0), VotorEvent::Timeout(0)));
assert!(matches!(events.remove(0), VotorEvent::Timeout(1)));
assert!(matches!(events.remove(0), VotorEvent::Timeout(2)));
assert!(matches!(events.remove(0), VotorEvent::Timeout(3)));
assert!(events.is_empty());
let stats = timers.stats();
assert_eq!(stats.set_timeout_count(), 1);
assert_eq!(stats.set_timeout_succeed_count(), 1);
assert_eq!(stats.max_heap_size(), 1);
}
#[test]
fn timer_state_with_multiplier() {
let delta_timeout = Duration::from_millis(100);
let delta_block = Duration::from_millis(50);
let now = Instant::now();
let slot = 0;
let multiplier = 1.5;
let (mut timer_state, next_fire) =
TimerState::new(slot, delta_timeout, delta_block, now, multiplier);
let expected_first_fire = now + Duration::from_millis(150);
assert!(
next_fire >= expected_first_fire - Duration::from_micros(100)
&& next_fire <= expected_first_fire + Duration::from_micros(100),
"Expected first fire around {expected_first_fire:?}, got {next_fire:?}",
);
assert!(matches!(
timer_state.progress(next_fire).unwrap(),
VotorEvent::TimeoutCrashedLeader(0)
));
let next = timer_state.next_fire().unwrap();
let expected_delta = Duration::from_millis(75);
let actual_delta = next - next_fire;
assert!(
actual_delta >= expected_delta - Duration::from_micros(100)
&& actual_delta <= expected_delta + Duration::from_micros(100),
"Expected delta around {expected_delta:?}, got {actual_delta:?}",
);
}
#[test]
fn test_calculate_timeout_multiplier() {
assert_eq!(calculate_timeout_multiplier(100, None), 1.0);
assert_eq!(calculate_timeout_multiplier(0, Some(0)), 1.0);
let multiplier = calculate_timeout_multiplier(4, Some(0));
assert!((multiplier - 1.05).abs() < 0.001);
let multiplier = calculate_timeout_multiplier(8, Some(0));
assert!((multiplier - 1.1025).abs() < 0.001);
let multiplier = calculate_timeout_multiplier(40, Some(0));
let expected = 1.05_f64.powi(10);
assert!((multiplier - expected).abs() < 0.001);
let multiplier = calculate_timeout_multiplier(28, Some(20));
assert!((multiplier - 1.1025).abs() < 0.001);
}
#[test]
fn timer_state_caps_at_max_timeout() {
let now = Instant::now();
let multiplier = 1000000.0;
let (mut timer_state, next_fire) = TimerState::new(
100,
DELTA_TIMEOUT,
Duration::from_millis(DEFAULT_MS_PER_SLOT),
now,
multiplier,
);
let expected_first_fire = now + Duration::from_secs(MAX_TIMEOUT_SECS as u64);
assert_eq!(next_fire, expected_first_fire);
assert!(matches!(
timer_state.progress(next_fire).unwrap(),
VotorEvent::TimeoutCrashedLeader(100)
));
let next = timer_state.next_fire().unwrap();
let actual_delta = next - next_fire;
assert_eq!(actual_delta, Duration::from_secs(MAX_TIMEOUT_SECS as u64));
}
}