use crate::fs::SegmentAccounting;
use std::collections::HashMap;
use std::future::Future;
use std::io;
use std::pin::Pin;
pub use crate::payload::Payload;
pub use crate::sealed::{MemorySegment, SealedSegment, SegmentRef};
pub struct SegmentData {
segment: SegmentRef,
payload: Payload,
metadata: HashMap<String, String>,
compressed_size: Option<u64>,
accounting: Option<SegmentAccounting>,
}
impl SegmentData {
pub(crate) fn new(
segment: SegmentRef,
payload: Payload,
metadata: HashMap<String, String>,
accounting: Option<SegmentAccounting>,
) -> Self {
Self {
segment,
payload,
metadata,
compressed_size: None,
accounting,
}
}
pub fn segment(&self) -> &SegmentRef {
&self.segment
}
pub fn payload(&self) -> &Payload {
&self.payload
}
pub fn take_payload(&mut self) -> Payload {
std::mem::take(&mut self.payload)
}
pub fn set_payload(&mut self, payload: impl Into<Payload>) {
self.payload = payload.into();
}
pub fn set_compressed_size(&mut self, bytes: u64) {
self.compressed_size = Some(bytes);
}
pub fn compressed_size(&self) -> Option<u64> {
self.compressed_size
}
pub fn metadata(&self) -> &HashMap<String, String> {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.metadata
}
pub fn adjust_accounting(&mut self) {
if let Some(acct) = self.accounting.as_mut() {
acct.adjust(self.payload.len() as u64);
}
}
#[cfg(test)]
pub(crate) fn accounting(&self) -> Option<&SegmentAccounting> {
self.accounting.as_ref()
}
}
impl std::fmt::Debug for SegmentData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SegmentData")
.field("segment", &self.segment)
.field("payload", &self.payload)
.field("metadata", &self.metadata)
.finish_non_exhaustive()
}
}
pub trait SegmentProcessor: Send {
fn name(&self) -> &'static str;
fn initialize(&mut self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>> {
Box::pin(std::future::ready(Ok(())))
}
fn process(
&mut self,
data: SegmentData,
) -> Pin<Box<dyn Future<Output = Result<SegmentData, ProcessError>> + Send + '_>>;
fn finalize_dump(
&mut self,
completion: &crate::dump::DumpCompletion,
) -> Pin<Box<dyn Future<Output = Option<String>> + Send + '_>> {
let _ = completion;
Box::pin(std::future::ready(None))
}
}
#[derive(Debug)]
pub struct ProcessError {
data: SegmentData,
kind: ProcessErrorKind,
}
impl ProcessError {
pub fn new(data: SegmentData, kind: ProcessErrorKind) -> Self {
Self { data, kind }
}
pub fn io(data: SegmentData, err: std::io::Error) -> Self {
Self::new(data, ProcessErrorKind::Io(err))
}
pub fn kind(&self) -> &ProcessErrorKind {
&self.kind
}
pub fn into_data(self) -> SegmentData {
self.data
}
pub fn into_parts(self) -> (SegmentData, ProcessErrorKind) {
(self.data, self.kind)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ProcessErrorKind {
#[non_exhaustive]
Io(std::io::Error),
#[non_exhaustive]
Transfer {
source: Box<dyn std::error::Error + Send + Sync>,
retryable: bool,
},
}
impl ProcessErrorKind {
pub fn transfer(source: Box<dyn std::error::Error + Send + Sync>, retryable: bool) -> Self {
Self::Transfer { source, retryable }
}
pub fn already_deleted(&self) -> bool {
matches!(self, ProcessErrorKind::Io(err) if err.kind() == io::ErrorKind::NotFound)
}
pub fn retryable(&self) -> bool {
match self {
ProcessErrorKind::Transfer { retryable, .. } => *retryable,
_ => false,
}
}
}
impl std::fmt::Display for ProcessErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Transfer { source, .. } => write!(f, "S3 transfer error: {source}"),
}
}
}
impl std::fmt::Display for ProcessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind.fmt(f)
}
}
impl std::error::Error for ProcessError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.kind {
ProcessErrorKind::Io(e) => Some(e),
ProcessErrorKind::Transfer { source, .. } => Some(source.as_ref()),
}
}
}
impl From<std::io::Error> for ProcessErrorKind {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::sync::Arc;
use crate::primitives::sync::atomic::{AtomicU64, Ordering};
#[test]
fn adjust_accounting_tracks_payload_size() {
let in_flight = Arc::new(AtomicU64::new(50));
let accounting = SegmentAccounting {
in_flight_bytes: Arc::clone(&in_flight),
in_flight_segments: Arc::new(AtomicU64::new(1)),
in_flight_bytes_peak: Arc::new(AtomicU64::new(50)),
size: 50,
};
let mut data = SegmentData::new(
SegmentRef::Disk(SealedSegment {
path: "x".into(),
index: 0,
}),
Payload::from_vec(vec![0u8; 50]),
HashMap::new(),
Some(accounting),
);
data.set_payload(Payload::from_vec(vec![0u8; 150]));
data.adjust_accounting();
assert_eq!(in_flight.load(Ordering::Acquire), 150);
data.set_payload(Payload::from_vec(vec![0u8; 5]));
data.adjust_accounting();
assert_eq!(in_flight.load(Ordering::Acquire), 5);
drop(data);
assert_eq!(in_flight.load(Ordering::Acquire), 0);
}
}