use crate::fact::{FactId, IoType};
use crate::phase::ReactorPhase;
use evorule_tcb::JsonValue;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::time::{Duration, Instant};
#[cfg(kani)]
mod kani_collections {
use crate::fact::FactId;
#[derive(Debug, Clone, Default)]
pub(crate) struct KIdSet(Vec<FactId>);
impl KIdSet {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn insert(&mut self, id: FactId) -> bool {
if self.0.iter().any(|x| x == &id) {
return false;
}
self.0.push(id);
true
}
pub fn remove(&mut self, id: &FactId) -> bool {
if let Some(pos) = self.0.iter().position(|x| x == id) {
self.0.swap_remove(pos);
true
} else {
false
}
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains(&self, id: &FactId) -> bool {
self.0.iter().any(|x| x == id)
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct KIdMap<V>(Vec<(FactId, V)>);
impl<V> KIdMap<V> {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn insert(&mut self, id: FactId, val: V) -> Option<V> {
if let Some(pos) = self.0.iter().position(|(k, _)| k == &id) {
Some(std::mem::replace(&mut self.0[pos].1, val))
} else {
self.0.push((id, val));
None
}
}
pub fn remove(&mut self, id: &FactId) -> Option<V> {
if let Some(pos) = self.0.iter().position(|(k, _)| k == id) {
Some(self.0.swap_remove(pos).1)
} else {
None
}
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains_key(&self, id: &FactId) -> bool {
self.0.iter().any(|(k, _)| k == id)
}
pub fn get(&self, id: &FactId) -> Option<&V> {
self.0.iter().find(|(k, _)| k == id).map(|(_, v)| v)
}
pub fn iter(&self) -> std::slice::Iter<'_, (FactId, V)> {
self.0.iter()
}
}
pub(crate) struct KIdMapIter<'a, V> {
inner: std::slice::Iter<'a, (FactId, V)>,
}
impl<'a, V> Iterator for KIdMapIter<'a, V> {
type Item = (&'a FactId, &'a V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, v)| (k, v))
}
}
impl<'a, V> IntoIterator for &'a KIdMap<V> {
type Item = (&'a FactId, &'a V);
type IntoIter = KIdMapIter<'a, V>;
fn into_iter(self) -> Self::IntoIter {
KIdMapIter {
inner: self.0.iter(),
}
}
}
pub(crate) struct KIdSetIter<'a> {
inner: std::slice::Iter<'a, FactId>,
}
impl<'a> Iterator for KIdSetIter<'a> {
type Item = &'a FactId;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl<'a> IntoIterator for &'a KIdSet {
type Item = &'a FactId;
type IntoIter = KIdSetIter<'a>;
fn into_iter(self) -> Self::IntoIter {
KIdSetIter {
inner: self.0.iter(),
}
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub(crate) struct ReactorState {
pub payload: JsonValue,
pub queue: VecDeque<JsonValue>,
pub version: u64,
pub prev_version: u64,
pub pending_io_count: usize,
#[cfg(not(kani))]
pub pending_requests: BTreeSet<FactId>,
#[cfg(kani)]
pub pending_requests: kani_collections::KIdSet,
#[cfg(not(kani))]
pub pending_io_instructions: BTreeMap<FactId, (JsonValue, FactId)>,
#[cfg(kani)]
pub pending_io_instructions: kani_collections::KIdMap<(JsonValue, FactId)>,
pub instruction_causes: VecDeque<FactId>,
#[cfg(not(kani))]
pub pending_io_timestamps: BTreeMap<FactId, Instant>,
#[cfg(kani)]
pub pending_io_timestamps: kani_collections::KIdMap<Instant>,
#[cfg(not(kani))]
pub pending_io_types: BTreeMap<FactId, IoType>,
#[cfg(kani)]
pub pending_io_types: kani_collections::KIdMap<IoType>,
pub io_recovery: bool,
pub phase: ReactorPhase,
pub structural_invariant_violations: u64,
#[cfg(kani)]
pub kani_has_io_result: bool,
}
#[allow(dead_code)] impl ReactorState {
pub fn new() -> Self {
Self {
payload: JsonValue::empty_object(),
queue: VecDeque::new(),
instruction_causes: VecDeque::new(),
version: 0,
prev_version: 0,
pending_io_count: 0,
#[cfg(not(kani))]
pending_requests: BTreeSet::new(),
#[cfg(kani)]
pending_requests: kani_collections::KIdSet::new(),
#[cfg(not(kani))]
pending_io_instructions: BTreeMap::new(),
#[cfg(kani)]
pending_io_instructions: kani_collections::KIdMap::new(),
#[cfg(not(kani))]
pending_io_timestamps: BTreeMap::new(),
#[cfg(kani)]
pending_io_timestamps: kani_collections::KIdMap::new(),
#[cfg(not(kani))]
pending_io_types: BTreeMap::new(),
#[cfg(kani)]
pending_io_types: kani_collections::KIdMap::new(),
io_recovery: false,
phase: ReactorPhase::default(),
structural_invariant_violations: 0,
#[cfg(kani)]
kani_has_io_result: false,
}
}
pub fn bump_version(&mut self) {
self.prev_version = self.version;
self.version += 1;
}
pub fn is_stable(&self) -> bool {
self.queue.is_empty() && self.pending_io_count == 0
}
pub fn queue_len(&self) -> usize {
self.queue.len()
}
pub fn pop_instruction(&mut self) -> Option<(JsonValue, FactId)> {
let instr = self.queue.pop_front()?;
let cause = self.instruction_causes.pop_front().unwrap_or(FactId(0));
Some((instr, cause))
}
pub fn push_front(&mut self, instruction: JsonValue, cause: FactId) {
self.queue.push_front(instruction);
self.instruction_causes.push_front(cause);
}
pub fn push_front_all(&mut self, instructions: Vec<JsonValue>, cause: FactId) {
for instr in instructions.into_iter().rev() {
self.queue.push_front(instr);
self.instruction_causes.push_front(cause);
}
}
pub fn push_back(&mut self, instruction: JsonValue, cause: FactId) {
self.queue.push_back(instruction);
self.instruction_causes.push_back(cause);
}
pub fn push_back_all(&mut self, instructions: Vec<JsonValue>, cause: FactId) {
for instr in instructions {
self.queue.push_back(instr);
self.instruction_causes.push_back(cause);
}
}
pub fn clear_queue(&mut self) {
self.queue.clear();
self.instruction_causes.clear();
}
pub fn update_queue_with_causes(&mut self, new_queue: Vec<JsonValue>, current_cause: FactId) {
let old_len = self.queue.len();
let old_causes: Vec<FactId> = self.instruction_causes.drain(..).collect();
let new_count = new_queue.len().saturating_sub(old_len);
self.queue = VecDeque::with_capacity(new_queue.len());
for (i, instr) in new_queue.into_iter().enumerate() {
let c = if i < new_count {
current_cause
} else {
old_causes
.get(i - new_count)
.copied()
.unwrap_or(current_cause)
};
self.queue.push_back(instr);
self.instruction_causes.push_back(c);
}
}
pub fn register_io_request(&mut self, id: FactId, io_type: IoType) {
if self.pending_requests.insert(id) {
self.pending_io_count += 1;
self.pending_io_timestamps.insert(id, Instant::now());
self.pending_io_types.insert(id, io_type);
}
}
pub fn complete_io_request(&mut self, id: FactId) -> bool {
if self.pending_requests.remove(&id) {
self.pending_io_count = self.pending_io_count.saturating_sub(1);
self.pending_io_timestamps.remove(&id);
self.pending_io_types.remove(&id);
true
} else {
false
}
}
pub fn scan_io_timeouts(
&self,
warn_timeout: Duration,
error_timeout: Duration,
) -> (Vec<FactId>, Vec<FactId>) {
let now = Instant::now();
let mut warn_ids = Vec::new();
let mut error_ids = Vec::new();
for (id, timestamp) in self.pending_io_timestamps.iter() {
let elapsed = now.duration_since(*timestamp);
if elapsed >= error_timeout {
error_ids.push(*id);
} else if elapsed >= warn_timeout {
warn_ids.push(*id);
}
}
(warn_ids, error_ids)
}
pub fn force_remove_io_request(&mut self, id: FactId) {
if self.pending_requests.remove(&id) {
self.pending_io_count = self.pending_io_count.saturating_sub(1);
self.pending_io_instructions.remove(&id);
self.pending_io_timestamps.remove(&id);
self.pending_io_types.remove(&id);
}
}
pub fn save_io_instruction(&mut self, id: FactId, instruction: JsonValue, cause: FactId) {
self.pending_io_instructions
.insert(id, (instruction, cause));
}
pub fn take_io_instruction(&mut self, id: FactId) -> Option<(JsonValue, FactId)> {
self.pending_io_instructions.remove(&id)
}
pub fn clear_io_result(&mut self) {
#[cfg(kani)]
{
self.kani_has_io_result = false;
return;
}
#[cfg(not(kani))]
{
if let JsonValue::Object(map) = &mut self.payload {
map.remove("__io_result__");
}
}
}
pub fn clear_io_recovery(&mut self) {
self.clear_io_result();
self.io_recovery = false;
}
}
impl Default for ReactorState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_state_initial() {
let state = ReactorState::new();
assert!(state.is_stable());
assert_eq!(state.version, 0);
assert_eq!(state.pending_io_count, 0);
assert!(state.pending_requests.is_empty());
}
#[test]
fn test_io_request_tracking() {
let mut state = ReactorState::new();
let id1 = FactId(1);
let id2 = FactId(2);
state.register_io_request(id1, IoType::call_external());
assert_eq!(state.pending_io_count, 1);
assert!(state.pending_requests.contains(&id1));
state.register_io_request(id2, IoType::call_external());
assert_eq!(state.pending_io_count, 2);
assert!(state.pending_requests.contains(&id2));
assert!(state.complete_io_request(id1));
assert_eq!(state.pending_io_count, 1);
assert!(!state.pending_requests.contains(&id1));
assert!(!state.complete_io_request(FactId(999)));
assert_eq!(state.pending_io_count, 1);
}
#[test]
fn test_push_front_all_preserves_order() {
let mut state = ReactorState::new();
let instrs = vec![
JsonValue::string("a"),
JsonValue::string("b"),
JsonValue::string("c"),
];
state.push_front_all(instrs, FactId(1));
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("a"), FactId(1)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("b"), FactId(1)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("c"), FactId(1)))
);
}
#[test]
fn test_push_back_all_preserves_order() {
let mut state = ReactorState::new();
let instrs = vec![
JsonValue::string("x"),
JsonValue::string("y"),
JsonValue::string("z"),
];
state.push_back_all(instrs, FactId(2));
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("x"), FactId(2)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("y"), FactId(2)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("z"), FactId(2)))
);
}
#[test]
fn test_push_front_single() {
let mut state = ReactorState::new();
state.push_back(JsonValue::string("a"), FactId(1));
state.push_back(JsonValue::string("b"), FactId(2));
state.push_front(JsonValue::string("urgent"), FactId(3));
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("urgent"), FactId(3)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("a"), FactId(1)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("b"), FactId(2)))
);
assert_eq!(state.pop_instruction(), None);
}
#[test]
fn test_push_front_all_to_empty_queue() {
let mut state = ReactorState::new();
state.push_front_all(vec![JsonValue::string("only")], FactId(1));
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("only"), FactId(1)))
);
assert_eq!(state.pop_instruction(), None);
}
#[test]
fn test_push_front_all_interleaved_with_existing() {
let mut state = ReactorState::new();
state.push_back(JsonValue::string("old"), FactId(1));
state.push_front_all(
vec![JsonValue::string("new1"), JsonValue::string("new2")],
FactId(2),
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("new1"), FactId(2)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("new2"), FactId(2)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("old"), FactId(1)))
);
}
#[test]
fn test_register_duplicate_io_request() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
assert_eq!(state.pending_io_count, 1);
assert_eq!(state.pending_requests.len(), 1);
state.register_io_request(id, IoType::call_external());
assert_eq!(state.pending_io_count, 1);
assert_eq!(state.pending_requests.len(), 1);
assert!(state.complete_io_request(id));
assert_eq!(state.pending_io_count, 0);
assert!(state.pending_requests.is_empty());
assert!(!state.complete_io_request(id));
assert_eq!(state.pending_io_count, 0);
}
#[test]
fn test_queue_len() {
let mut state = ReactorState::new();
assert_eq!(state.queue_len(), 0);
state.push_back(JsonValue::string("a"), FactId(1));
assert_eq!(state.queue_len(), 1);
state.push_back(JsonValue::string("b"), FactId(2));
assert_eq!(state.queue_len(), 2);
state.pop_instruction();
assert_eq!(state.queue_len(), 1);
}
#[test]
fn test_pop_from_empty_queue() {
let mut state = ReactorState::new();
assert_eq!(state.pop_instruction(), None);
}
#[test]
fn test_instruction_causes_synced_with_queue() {
let mut state = ReactorState::new();
assert_eq!(state.instruction_causes.len(), state.queue.len());
state.push_back(JsonValue::string("a"), FactId(10));
state.push_back(JsonValue::string("b"), FactId(20));
assert_eq!(state.instruction_causes.len(), state.queue.len());
state.pop_instruction();
assert_eq!(state.instruction_causes.len(), state.queue.len());
state.clear_queue();
assert_eq!(state.instruction_causes.len(), state.queue.len());
assert_eq!(state.instruction_causes.len(), 0);
}
#[test]
fn test_update_queue_with_causes_new_push() {
let mut state = ReactorState::new();
state.push_back(JsonValue::string("B"), FactId(2));
state.push_back(JsonValue::string("C"), FactId(3));
let current_cause = FactId(1);
let new_queue = vec![
JsonValue::string("X"),
JsonValue::string("Y"),
JsonValue::string("B"),
JsonValue::string("C"),
];
state.update_queue_with_causes(new_queue, current_cause);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("X"), FactId(1)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("Y"), FactId(1)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("B"), FactId(2)))
);
assert_eq!(
state.pop_instruction(),
Some((JsonValue::string("C"), FactId(3)))
);
}
#[test]
fn test_complete_io_request_empty() {
let mut state = ReactorState::new();
assert!(!state.complete_io_request(FactId(999)));
assert_eq!(state.pending_io_count, 0);
}
#[test]
fn test_default_state() {
let state = ReactorState::default();
assert!(state.is_stable());
assert_eq!(state.version, 0);
assert_eq!(state.queue_len(), 0);
}
#[test]
fn test_register_io_request_records_timestamp() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
assert!(state.pending_io_timestamps.contains_key(&id));
}
#[test]
fn test_complete_io_request_removes_timestamp() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
assert!(state.pending_io_timestamps.contains_key(&id));
assert!(state.complete_io_request(id));
assert!(!state.pending_io_timestamps.contains_key(&id));
}
#[test]
fn test_scan_io_timeouts_no_pending() {
let state = ReactorState::new();
let (warn_ids, error_ids) =
state.scan_io_timeouts(Duration::from_secs(30), Duration::from_secs(60));
assert!(warn_ids.is_empty());
assert!(error_ids.is_empty());
}
#[test]
fn test_scan_io_timeouts_warn_level() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
state
.pending_io_timestamps
.insert(id, Instant::now() - Duration::from_secs(35));
let (warn_ids, error_ids) =
state.scan_io_timeouts(Duration::from_secs(30), Duration::from_secs(60));
assert_eq!(warn_ids, vec![FactId(1)]);
assert!(error_ids.is_empty());
}
#[test]
fn test_scan_io_timeouts_error_level() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
state
.pending_io_timestamps
.insert(id, Instant::now() - Duration::from_secs(65));
let (warn_ids, error_ids) =
state.scan_io_timeouts(Duration::from_secs(30), Duration::from_secs(60));
assert!(warn_ids.is_empty());
assert_eq!(error_ids, vec![FactId(1)]);
}
#[test]
fn test_scan_io_timeouts_mixed() {
let mut state = ReactorState::new();
let warn_id = FactId(1);
let error_id = FactId(2);
let normal_id = FactId(3);
state.register_io_request(warn_id, IoType::call_external());
state.register_io_request(error_id, IoType::call_external());
state.register_io_request(normal_id, IoType::call_external());
state
.pending_io_timestamps
.insert(warn_id, Instant::now() - Duration::from_secs(40));
state
.pending_io_timestamps
.insert(error_id, Instant::now() - Duration::from_secs(70));
let (warn_ids, error_ids) =
state.scan_io_timeouts(Duration::from_secs(30), Duration::from_secs(60));
assert_eq!(warn_ids, vec![FactId(1)]);
assert_eq!(error_ids, vec![FactId(2)]);
}
#[test]
fn test_force_remove_io_request() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
state.save_io_instruction(id, JsonValue::string("original_instruction"), FactId(100));
assert_eq!(state.pending_io_count, 1);
assert!(state.pending_requests.contains(&id));
assert!(state.pending_io_instructions.contains_key(&id));
assert!(state.pending_io_timestamps.contains_key(&id));
assert!(state.pending_io_types.contains_key(&id));
state.force_remove_io_request(id);
assert_eq!(state.pending_io_count, 0);
assert!(!state.pending_requests.contains(&id));
assert!(!state.pending_io_instructions.contains_key(&id));
assert!(!state.pending_io_timestamps.contains_key(&id));
assert!(!state.pending_io_types.contains_key(&id));
}
#[test]
fn test_force_remove_nonexistent_io_request() {
let mut state = ReactorState::new();
state.force_remove_io_request(FactId(999));
assert_eq!(state.pending_io_count, 0);
}
#[test]
fn test_register_io_request_records_io_type() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::query_db());
assert_eq!(state.pending_io_types.get(&id), Some(&IoType::query_db()));
}
#[test]
fn test_complete_io_request_removes_io_type() {
let mut state = ReactorState::new();
let id = FactId(1);
state.register_io_request(id, IoType::call_external());
assert!(state.pending_io_types.contains_key(&id));
assert!(state.complete_io_request(id));
assert!(!state.pending_io_types.contains_key(&id));
}
#[test]
fn test_register_multiple_io_types() {
let mut state = ReactorState::new();
state.register_io_request(FactId(1), IoType::call_external());
state.register_io_request(FactId(2), IoType::query_db());
state.register_io_request(FactId(3), IoType::http_get());
assert_eq!(state.pending_io_types.len(), 3);
assert_eq!(
state.pending_io_types.get(&FactId(1)),
Some(&IoType::call_external())
);
assert_eq!(
state.pending_io_types.get(&FactId(2)),
Some(&IoType::query_db())
);
assert_eq!(
state.pending_io_types.get(&FactId(3)),
Some(&IoType::http_get())
);
}
}