use std::io;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use minarrow::{SuperTable, Table};
pub trait ChunkedTableWriter: Sized {
type Error: From<io::Error>;
fn extension() -> &'static str;
fn dir(&self) -> &Path;
fn base(&self) -> &str;
fn counter(&self) -> &AtomicU64;
fn batches_written(&self) -> u64 {
self.counter().load(Ordering::Relaxed)
}
fn chunk_path_for(&self, index: u64) -> PathBuf {
self.dir().join(format!(
"{}-{:010}.{}",
self.base(),
index,
Self::extension()
))
}
fn write_chunk_at(&self, path: &Path, table: &Table) -> Result<(), Self::Error>;
fn write_chunk(&mut self, table: &Table) -> Result<PathBuf, Self::Error> {
let idx = self.counter().fetch_add(1, Ordering::Relaxed);
let path = self.chunk_path_for(idx);
self.write_chunk_at(&path, table)?;
Ok(path)
}
fn write_all(&mut self, supertable: &SuperTable) -> Result<Vec<PathBuf>, Self::Error> {
let mut paths = Vec::with_capacity(supertable.batches.len());
for batch in supertable.batches.iter() {
paths.push(self.write_chunk(batch.as_ref())?);
}
Ok(paths)
}
fn par_write_all(
&self,
tables: &[&Table],
threads: Option<usize>,
) -> Result<Vec<PathBuf>, Self::Error>
where
Self: Sync,
Self::Error: Send,
{
if tables.is_empty() {
return Ok(Vec::new());
}
let default_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let n_threads = threads.unwrap_or(default_threads).max(1).min(tables.len());
let n_files = tables.len();
let start = self.counter().fetch_add(n_files as u64, Ordering::Relaxed);
let cursor = AtomicU64::new(0);
let per_worker_cap = n_files.div_ceil(n_threads);
let collected: Result<Vec<(u64, PathBuf)>, Self::Error> = std::thread::scope(|s| {
let mut handles = Vec::with_capacity(n_threads);
for _ in 0..n_threads {
let cursor = &cursor;
let tables = &tables;
let me = &*self;
handles.push(
s.spawn(move || -> Result<Vec<(u64, PathBuf)>, Self::Error> {
let mut local: Vec<(u64, PathBuf)> = Vec::with_capacity(per_worker_cap);
loop {
let i = cursor.fetch_add(1, Ordering::Relaxed);
if i as usize >= n_files {
break;
}
let idx = start + i;
let path = me.chunk_path_for(idx);
me.write_chunk_at(&path, tables[i as usize])?;
local.push((i, path));
}
Ok(local)
}),
);
}
let mut all: Vec<(u64, PathBuf)> = Vec::with_capacity(n_files);
for h in handles {
let part = h.join().expect("chunked writer worker panicked")?;
all.extend(part);
}
Ok(all)
});
let mut all = collected?;
all.sort_by_key(|(i, _)| *i);
Ok(all.into_iter().map(|(_, p)| p).collect())
}
}