blob: 3d9ad21bfa4a8f800317d73a66785db62f4076c0 [file]
use crate::state::{State, StateEntry};
use crate::streaming::persistence::persister::Persister;
use crate::streaming::utils::file;
use crate::versioning::SemanticVersion;
use async_trait::async_trait;
use bytes::{BufMut, Bytes, BytesMut};
use iggy::bytes_serializable::BytesSerializable;
use iggy::error::IggyError;
use iggy::utils::byte_size::IggyByteSize;
use iggy::utils::timestamp::IggyTimestamp;
use log::debug;
use std::fmt::Debug;
use std::path::Path;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, BufReader};
use tracing::info;
const BUF_READER_CAPACITY_BYTES: usize = 512 * 1000;
#[derive(Debug)]
pub struct FileState {
current_index: AtomicU64,
entries_count: AtomicU64,
current_leader: AtomicU32,
term: AtomicU64,
version: u32,
path: String,
persister: Arc<dyn Persister>,
}
impl FileState {
pub fn new(path: &str, version: &SemanticVersion, persister: Arc<dyn Persister>) -> Self {
Self {
current_index: AtomicU64::new(0),
entries_count: AtomicU64::new(0),
current_leader: AtomicU32::new(0),
term: AtomicU64::new(0),
path: path.into(),
persister,
version: version.get_numeric_version().expect("Invalid version"),
}
}
pub fn current_index(&self) -> u64 {
self.current_index.load(Ordering::SeqCst)
}
pub fn entries_count(&self) -> u64 {
self.entries_count.load(Ordering::SeqCst)
}
pub fn term(&self) -> u64 {
self.term.load(Ordering::SeqCst)
}
}
#[async_trait]
impl State for FileState {
async fn init(&self) -> Result<Vec<StateEntry>, IggyError> {
if !Path::new(&self.path).exists() {
info!("State file does not exist, creating a new one");
self.persister.overwrite(&self.path, &[]).await?;
}
let entries = self.load_entries().await?;
self.entries_count
.store(entries.len() as u64, Ordering::SeqCst);
if !entries.is_empty() {
self.current_index
.store((entries.len() - 1) as u64, Ordering::SeqCst);
}
return Ok(entries);
}
async fn load_entries(&self) -> Result<Vec<StateEntry>, IggyError> {
if !Path::new(&self.path).exists() {
return Err(IggyError::StateFileNotFound);
}
let file = file::open(&self.path).await?;
let file_size = file.metadata().await?.len();
if file_size == 0 {
info!("State file is empty");
return Ok(Vec::new());
}
info!(
"Loading state, file size: {}",
IggyByteSize::from(file_size).as_human_string()
);
let mut entries = Vec::new();
let mut total_size: u64 = 0;
let mut reader = BufReader::with_capacity(BUF_READER_CAPACITY_BYTES, file);
loop {
let index = reader.read_u64_le().await?;
let term = reader.read_u64_le().await?;
let leader_id = reader.read_u32_le().await?;
let version = reader.read_u32_le().await?;
let flags = reader.read_u64_le().await?;
let timestamp = IggyTimestamp::from(reader.read_u64_le().await?);
let user_id = reader.read_u32_le().await?;
let code = reader.read_u32_le().await?;
let payload_length = reader.read_u32_le().await? as usize;
let mut payload = BytesMut::with_capacity(payload_length);
payload.put_bytes(0, payload_length);
reader.read_exact(&mut payload).await?;
let context_length = reader.read_u32_le().await? as usize;
let mut context = BytesMut::with_capacity(context_length);
context.put_bytes(0, context_length);
reader.read_exact(&mut context).await?;
let entry = StateEntry {
index,
term,
leader_id,
version,
flags,
timestamp,
user_id,
code,
payload: payload.freeze(),
context: context.freeze(),
};
debug!("Read state entry: {entry}");
entries.push(entry);
total_size += 8
+ 8
+ 4
+ 4
+ 8
+ 8
+ 4
+ 4
+ 4
+ payload_length as u64
+ 4
+ context_length as u64;
if total_size == file_size {
break;
}
}
info!("Loaded {} state entries", entries.len());
Ok(entries)
}
async fn apply(
&self,
code: u32,
user_id: u32,
payload: &[u8],
context: Option<&[u8]>,
) -> Result<(), IggyError> {
debug!("Applying state entry with code: {code}, user ID: {user_id}");
let entry = StateEntry {
index: if self.entries_count.load(Ordering::SeqCst) == 0 {
0
} else {
self.current_index.fetch_add(1, Ordering::SeqCst) + 1
},
term: self.term.load(Ordering::SeqCst),
leader_id: self.current_leader.load(Ordering::SeqCst),
version: self.version,
flags: 0,
timestamp: IggyTimestamp::now(),
user_id,
code,
payload: Bytes::copy_from_slice(payload),
context: context.map_or(Bytes::new(), Bytes::copy_from_slice),
};
self.entries_count.fetch_add(1, Ordering::SeqCst);
self.persister.append(&self.path, &entry.as_bytes()).await?;
debug!(
"Applied state entry with code: {code}, user ID: {user_id}, {}",
entry
);
Ok(())
}
}