mod core;
mod error;
mod io;
mod session;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use std::sync::Mutex;
pub use error::ParallelEncodeError;
pub use io::{ParallelEncoderReader, ParallelEncoderWriter};
pub use session::{
OwnedParallelTask, ParallelEncoderSession, ParallelEncoderSessionOwned, ParallelEncoderStatus,
ParallelProgress, ParallelTaskQueue, ParallelTasks,
};
use self::core::{EnginePool, FramePlan, compress_segment, compress_serial, lock};
use super::config::{ConfigError, EncoderConfig, Parameter};
use super::core::cctx::RequestedParams;
use super::error::EncodeError;
use super::internal::requested_params;
use crate::shared::xxhash::Xxh64;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SegmentSize(usize);
impl SegmentSize {
pub const MIN: Self = Self(64 << 10);
pub const MAX: Self = Self(256 << 20);
pub const DEFAULT: Self = Self(4 << 20);
#[must_use]
pub const fn get(self) -> usize {
self.0
}
}
impl Default for SegmentSize {
fn default() -> Self {
Self::DEFAULT
}
}
impl TryFrom<usize> for SegmentSize {
type Error = ConfigError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
if !(Self::MIN.0..=Self::MAX.0).contains(&value) {
return Err(ConfigError::OutOfRange {
parameter: Parameter::SegmentSize,
value: value as i64,
min: Self::MIN.0 as i64,
max: Self::MAX.0 as i64,
});
}
Ok(Self(value))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TaskCount(usize);
impl TaskCount {
pub const ONE: Self = Self(1);
pub const MAX: Self = Self(1 << 16);
#[must_use]
pub const fn get(self) -> usize {
self.0
}
}
impl Default for TaskCount {
fn default() -> Self {
Self::ONE
}
}
impl TryFrom<usize> for TaskCount {
type Error = ConfigError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
if value == 0 || value > Self::MAX.0 {
return Err(ConfigError::OutOfRange {
parameter: Parameter::TaskCount,
value: value as i64,
min: 1,
max: Self::MAX.0 as i64,
});
}
Ok(Self(value))
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct ParallelConfig {
segment_size: SegmentSize,
}
impl ParallelConfig {
#[must_use]
pub const fn with_segment_size(mut self, segment_size: SegmentSize) -> Self {
self.segment_size = segment_size;
self
}
#[must_use]
pub const fn segment_size(&self) -> SegmentSize {
self.segment_size
}
}
#[derive(Debug)]
pub struct ParallelCompressor {
config: EncoderConfig,
requested: RequestedParams,
parallel: ParallelConfig,
pool: Arc<EnginePool>,
}
impl ParallelCompressor {
pub fn new(config: EncoderConfig, parallel: ParallelConfig) -> Result<Self, ConfigError> {
config.validate()?;
Ok(Self {
config,
requested: requested_params(&config),
parallel,
pool: Arc::new(EnginePool::default()),
})
}
#[must_use]
pub const fn config(&self) -> &EncoderConfig {
&self.config
}
#[must_use]
pub const fn parallel_config(&self) -> &ParallelConfig {
&self.parallel
}
#[must_use]
pub fn retained_bytes(&self) -> usize {
self.pool.retained_bytes()
}
pub fn release(&mut self) {
self.pool.release();
}
pub fn prepare_slice<'a>(
&mut self,
input: &'a [u8],
tasks: TaskCount,
) -> ScopedParallelBatch<'a> {
let segment = self.parallel.segment_size.get();
let shared = Arc::new(BatchShared::default());
let plan = FramePlan::new(&self.requested, Some(input.len() as u64));
if input.len() <= segment {
let task = ScopedParallelTask {
work: Work::Serial {
requested: self.requested,
input,
},
index: 0,
segment: 0,
shared: Arc::clone(&shared),
pool: Arc::clone(&self.pool),
running: false,
published: false,
};
return ScopedParallelBatch {
tasks: vec![task],
shared,
expected: 1,
serial: true,
plan,
input,
};
}
let segments = input.len().div_ceil(segment);
let task_count = tasks.get().min(segments);
let per_task = segments / task_count;
let extra = segments % task_count;
let mut list = Vec::with_capacity(task_count);
let mut first = 0;
for t in 0..task_count {
let count = per_task + usize::from(t < extra);
list.push(ScopedParallelTask {
work: Work::Segments {
plan,
input,
first,
count,
segment,
},
index: t,
segment: first as u64,
shared: Arc::clone(&shared),
pool: Arc::clone(&self.pool),
running: false,
published: false,
});
first += count;
}
ScopedParallelBatch {
tasks: list,
shared,
expected: task_count,
serial: false,
plan,
input,
}
}
pub fn compress(&mut self, input: &[u8]) -> Result<Vec<u8>, ParallelEncodeError> {
self.prepare_slice(input, TaskCount::ONE).finish()
}
}
type Slot = (usize, Result<Vec<u8>, ParallelEncodeError>);
#[derive(Debug, Default)]
struct BatchShared {
slots: Mutex<Vec<Slot>>,
}
#[derive(Debug)]
enum Work<'a> {
Serial {
requested: RequestedParams,
input: &'a [u8],
},
Segments {
plan: FramePlan,
input: &'a [u8],
first: usize,
count: usize,
segment: usize,
},
}
#[derive(Debug)]
pub struct ScopedParallelTask<'a> {
work: Work<'a>,
index: usize,
segment: u64,
shared: Arc<BatchShared>,
pool: Arc<EnginePool>,
running: bool,
published: bool,
}
impl ScopedParallelTask<'_> {
pub fn run(mut self) {
self.running = true;
let mut engine = self.pool.take();
let result = match &self.work {
Work::Serial { requested, input } => compress_serial(&mut engine, requested, input),
Work::Segments {
plan,
input,
first,
count,
segment,
} => {
let overlap = plan.overlap();
let mut out = Vec::new();
let mut result = Ok(());
for s in *first..first + count {
let start = s * segment;
let end = (start + segment).min(input.len());
let from = start.saturating_sub(overlap);
match compress_segment(
&mut engine,
plan,
&input[from..end],
start - from,
&mut out,
) {
Ok(()) => {}
Err(error) => {
result = Err(EncodeError::from_core(error, 0, None));
break;
}
}
}
result.map(|()| out)
}
};
self.pool.give(engine);
self.publish(result.map_err(ParallelEncodeError::from));
}
fn publish(&mut self, result: Result<Vec<u8>, ParallelEncodeError>) {
self.published = true;
lock(&self.shared.slots).push((self.index, result));
}
}
impl Drop for ScopedParallelTask<'_> {
fn drop(&mut self) {
if !self.published {
let segment = self.segment;
self.publish(Err(if self.running {
ParallelEncodeError::TaskPanicked { segment }
} else {
ParallelEncodeError::TaskAbandoned { segment }
}));
}
}
}
#[derive(Debug)]
pub struct ScopedParallelBatch<'a> {
tasks: Vec<ScopedParallelTask<'a>>,
shared: Arc<BatchShared>,
expected: usize,
serial: bool,
plan: FramePlan,
input: &'a [u8],
}
impl<'a> ScopedParallelBatch<'a> {
pub fn take_tasks(&mut self) -> Vec<ScopedParallelTask<'a>> {
::core::mem::take(&mut self.tasks)
}
pub fn finish(self) -> Result<Vec<u8>, ParallelEncodeError> {
let mut out = Vec::new();
self.finish_into(&mut out)?;
Ok(out)
}
pub fn finish_into(mut self, out: &mut Vec<u8>) -> Result<(), ParallelEncodeError> {
for task in ::core::mem::take(&mut self.tasks) {
task.run();
}
let mut slots = ::core::mem::take(&mut *lock(&self.shared.slots));
if slots.len() != self.expected {
return Err(ParallelEncodeError::InvalidState);
}
slots.sort_by_key(|(index, _)| *index);
let mut parts = Vec::with_capacity(slots.len());
for (_, result) in slots {
match result {
Ok(bytes) => parts.push(bytes),
Err(error) => return Err(ParallelEncodeError::BatchFailed(Arc::new(error))),
}
}
if self.serial {
parts.iter().for_each(|part| out.extend_from_slice(part));
return Ok(());
}
let header = self.plan.header();
out.reserve(header.len() + parts.iter().map(Vec::len).sum::<usize>() + 7);
out.extend_from_slice(&header);
parts.iter().for_each(|part| out.extend_from_slice(part));
let mut hasher = Xxh64::new(0);
if self.plan.applied.fparams.checksum {
hasher.update(self.input);
}
out.extend_from_slice(&self.plan.epilogue(&hasher));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_task_that_unwinds_reports_a_panic_to_its_batch() {
let mut compressor =
ParallelCompressor::new(EncoderConfig::default(), ParallelConfig::default()).unwrap();
let mut batch = compressor.prepare_slice(b"input", TaskCount::ONE);
let mut tasks = batch.take_tasks();
tasks[0].running = true;
drop(tasks);
assert_eq!(
batch.finish(),
Err(ParallelEncodeError::BatchFailed(Arc::new(
ParallelEncodeError::TaskPanicked { segment: 0 }
)))
);
}
}