use std::time::{Duration, Instant};
use crate::error::Error;
pub const ATTENTION_TIMEOUT_SECONDS: u64 = 5;
#[derive(Debug)]
pub(crate) struct BulkCopyTimeoutState {
deadline: Option<Instant>,
attention_sent: bool,
attention_received: bool,
attention_sending: bool,
bulk_copy_write_timeout: bool,
#[allow(dead_code)] pre_attention_errors: Vec<Error>,
}
impl BulkCopyTimeoutState {
pub fn new(timeout: Option<Duration>) -> Self {
let deadline = timeout.map(|t| Instant::now() + t);
Self {
deadline,
attention_sent: false,
attention_received: false,
attention_sending: false,
bulk_copy_write_timeout: false,
pre_attention_errors: Vec::new(),
}
}
pub fn from_seconds(timeout_sec: u32) -> Self {
if timeout_sec == 0 {
Self::new(None)
} else {
Self::new(Some(Duration::from_secs(timeout_sec as u64)))
}
}
#[inline]
pub fn is_expired(&self) -> bool {
self.deadline.is_some_and(|d| Instant::now() >= d)
}
pub fn remaining_ms(&self) -> Option<u64> {
self.deadline
.map(|d| d.saturating_duration_since(Instant::now()).as_millis() as u64)
}
#[cfg(test)]
pub fn remaining_duration(&self) -> Option<Duration> {
self.deadline
.map(|d| d.saturating_duration_since(Instant::now()))
}
pub fn set_attention_timeout(&mut self) {
self.deadline = Some(Instant::now() + Duration::from_secs(ATTENTION_TIMEOUT_SECONDS));
}
#[inline]
pub fn is_attention_sent(&self) -> bool {
self.attention_sent
}
#[cfg(test)]
#[inline]
pub fn is_attention_received(&self) -> bool {
self.attention_received
}
#[cfg(test)]
#[inline]
pub fn is_attention_sending(&self) -> bool {
self.attention_sending
}
#[cfg(test)]
#[inline]
pub fn is_bulk_copy_write_timeout(&self) -> bool {
self.bulk_copy_write_timeout
}
pub fn begin_sending_attention(&mut self) {
self.attention_sending = true;
}
pub fn mark_attention_sent(&mut self) {
self.attention_sending = false;
self.attention_sent = true;
self.set_attention_timeout();
}
pub fn mark_attention_received(&mut self) {
self.attention_received = true;
}
pub fn mark_bulk_copy_write_timeout(&mut self) {
self.bulk_copy_write_timeout = true;
}
#[cfg(test)]
pub fn store_errors_for_attention(&mut self, errors: Vec<Error>) {
self.pre_attention_errors = errors;
}
#[cfg(test)]
pub fn restore_errors_after_attention(&mut self) -> Vec<Error> {
std::mem::take(&mut self.pre_attention_errors)
}
#[cfg(test)]
pub fn reset_attention_state(&mut self) {
self.attention_sent = false;
self.attention_received = false;
self.attention_sending = false;
self.bulk_copy_write_timeout = false;
self.pre_attention_errors.clear();
}
#[cfg(test)]
pub fn is_attention_complete(&self) -> bool {
self.attention_sent && self.attention_received
}
}
impl Default for BulkCopyTimeoutState {
fn default() -> Self {
Self::new(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timeout_state_creation() {
let state = BulkCopyTimeoutState::new(Some(Duration::from_secs(30)));
assert!(!state.is_expired());
assert!(!state.is_attention_sent());
assert!(!state.is_attention_received());
assert!(!state.is_attention_sending());
assert!(!state.is_bulk_copy_write_timeout());
}
#[test]
fn test_timeout_state_from_seconds() {
let state = BulkCopyTimeoutState::from_seconds(30);
assert!(!state.is_expired());
assert!(state.remaining_ms().is_some());
let infinite = BulkCopyTimeoutState::from_seconds(0);
assert!(!infinite.is_expired());
assert!(infinite.remaining_ms().is_none());
}
#[test]
fn test_infinite_timeout_never_expires() {
let state = BulkCopyTimeoutState::new(None);
assert!(!state.is_expired());
assert!(state.remaining_ms().is_none());
assert!(state.remaining_duration().is_none());
}
#[test]
fn test_timeout_expiry() {
let state = BulkCopyTimeoutState::new(Some(Duration::from_millis(10)));
assert!(!state.is_expired());
std::thread::sleep(Duration::from_millis(20));
assert!(state.is_expired());
}
#[test]
fn test_remaining_ms_calculation() {
let state = BulkCopyTimeoutState::new(Some(Duration::from_secs(10)));
let remaining = state.remaining_ms().unwrap();
assert!(remaining > 9900);
assert!(remaining <= 10000);
}
#[test]
fn test_attention_timeout_5_seconds() {
let mut state = BulkCopyTimeoutState::new(None);
state.set_attention_timeout();
let remaining = state.remaining_ms().unwrap();
assert!(remaining > 4900);
assert!(remaining <= 5000);
}
#[test]
fn test_attention_state_transitions() {
let mut state = BulkCopyTimeoutState::new(Some(Duration::from_secs(30)));
assert!(!state.is_attention_sending());
assert!(!state.is_attention_sent());
assert!(!state.is_attention_received());
state.begin_sending_attention();
assert!(state.is_attention_sending());
assert!(!state.is_attention_sent());
state.mark_attention_sent();
assert!(!state.is_attention_sending());
assert!(state.is_attention_sent());
state.mark_attention_received();
assert!(state.is_attention_received());
assert!(state.is_attention_complete());
}
#[test]
fn test_bulk_copy_write_timeout_flag() {
let mut state = BulkCopyTimeoutState::new(Some(Duration::from_secs(30)));
assert!(!state.is_bulk_copy_write_timeout());
state.mark_bulk_copy_write_timeout();
assert!(state.is_bulk_copy_write_timeout());
}
#[test]
fn test_error_preservation() {
let mut state = BulkCopyTimeoutState::new(None);
let errors = vec![
Error::ProtocolError("Error 1".to_string()),
Error::ProtocolError("Error 2".to_string()),
];
state.store_errors_for_attention(errors);
let restored = state.restore_errors_after_attention();
assert_eq!(restored.len(), 2);
let empty = state.restore_errors_after_attention();
assert!(empty.is_empty());
}
#[test]
fn test_reset_attention_state() {
let mut state = BulkCopyTimeoutState::new(Some(Duration::from_secs(30)));
state.mark_bulk_copy_write_timeout();
state.begin_sending_attention();
state.mark_attention_sent();
state.mark_attention_received();
state.store_errors_for_attention(vec![Error::ProtocolError("test".to_string())]);
state.reset_attention_state();
assert!(!state.is_attention_sending());
assert!(!state.is_attention_sent());
assert!(!state.is_attention_received());
assert!(!state.is_bulk_copy_write_timeout());
assert!(state.restore_errors_after_attention().is_empty());
}
#[test]
fn test_default_is_infinite() {
let state = BulkCopyTimeoutState::default();
assert!(!state.is_expired());
assert!(state.remaining_ms().is_none());
}
}