use super::blob::{BlobReader, BlobType, DecompressPool};
use super::block::PrimitiveBlock;
use super::pipeline_metrics::{elapsed_ns_u64, PIPELINE_METRICS};
use crate::blob_meta::BlobFilter;
use crate::error::Result;
use crate::reorder_buffer::ReorderBuffer;
use std::cell::RefCell;
use std::io::Read;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::mpsc::sync_channel;
use std::sync::Arc;
use std::time::Instant;
fn should_skip_blob(filter: &BlobFilter, blob: &super::blob::Blob) -> bool {
if let Some(idx) = blob.index()
&& !filter.wants_index(&idx)
{
return true;
}
if filter.has_tag_filter()
&& let Some(tag_idx) = blob.tag_index()
&& !filter.wants_tag_index(&tag_idx)
{
return true;
}
false
}
pub(crate) const DEFAULT_READ_AHEAD: usize = 16;
pub(crate) const DEFAULT_DECODE_AHEAD: usize = 32;
#[derive(Clone, Copy, Debug)]
pub(crate) struct PipelineConfig {
pub(crate) read_ahead: usize,
pub(crate) decode_ahead: usize,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
read_ahead: DEFAULT_READ_AHEAD,
decode_ahead: DEFAULT_DECODE_AHEAD,
}
}
}
#[allow(clippy::needless_pass_by_value)]
#[allow(clippy::too_many_lines)]
#[hotpath::measure]
pub(crate) fn run_pipeline<R, F>(
mut blob_reader: BlobReader<R>,
decode_thread_count: Option<usize>,
pipeline_config: PipelineConfig,
blob_filter: Option<BlobFilter>,
mut block_fn: F,
) -> Result<()>
where
R: Read + Send,
F: FnMut(PrimitiveBlock) -> Result<()>,
{
type RawItem = (usize, crate::error::Result<crate::blob::Blob>);
type DecodedItem = (usize, Option<crate::error::Result<PrimitiveBlock>>);
let has_tag_filter = blob_filter.as_ref().is_some_and(BlobFilter::has_tag_filter);
blob_reader.set_parse_tagdata(has_tag_filter);
blob_reader.set_parse_indexdata(blob_filter.is_some());
let blob_filter = blob_filter.map(Arc::new);
let (raw_tx, raw_rx) = sync_channel::<RawItem>(pipeline_config.read_ahead);
let (decoded_tx, decoded_rx) = sync_channel::<DecodedItem>(pipeline_config.decode_ahead);
std::thread::scope(|scope| {
scope.spawn(move || {
for (seq, blob_result) in blob_reader.enumerate() {
let t_send = Instant::now();
let send_result = raw_tx.send((seq, blob_result));
PIPELINE_METRICS
.raw_send_wait_ns
.fetch_add(elapsed_ns_u64(t_send), Relaxed);
if send_result.is_err() {
break; }
}
});
let dispatch_tx = decoded_tx.clone();
scope.spawn(move || {
let decode_threads = decode_thread_count.unwrap_or_else(|| {
std::thread::available_parallelism()
.map(|n| n.get().saturating_sub(2).max(1))
.unwrap_or(4)
});
let decode_pool = match rayon::ThreadPoolBuilder::new()
.num_threads(decode_threads)
.build()
{
Ok(pool) => pool,
Err(e) => {
let err = crate::error::new_error(crate::error::ErrorKind::Io(
std::io::Error::other(format!("failed to build decode pool: {e}")),
));
drop(dispatch_tx.send((0, Some(Err(err)))));
return;
}
};
let buffer_pool = DecompressPool::new();
for (seq, blob_result) in raw_rx {
let tx = dispatch_tx.clone();
let bp = Arc::clone(&buffer_pool);
match blob_result {
Ok(blob) => {
let bf = blob_filter.clone();
PIPELINE_METRICS.decode_tasks.fetch_add(1, Relaxed);
decode_pool.spawn(move || {
thread_local! {
static ST_SCRATCH: RefCell<Vec<(u32, u32)>> = const { RefCell::new(Vec::new()) };
static GR_SCRATCH: RefCell<Vec<(u32, u32)>> = const { RefCell::new(Vec::new()) };
}
let item = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
match blob.get_type() {
BlobType::OsmData => {
if let Some(ref filter) = bf
&& should_skip_blob(filter, &blob)
{
PIPELINE_METRICS
.blobs_skipped_by_filter
.fetch_add(1, Relaxed);
return None;
}
ST_SCRATCH.with_borrow_mut(|st| {
GR_SCRATCH.with_borrow_mut(|gr| {
let result = blob
.to_primitiveblock_inline_with_scratch(&bp, st, gr);
PIPELINE_METRICS.record_scratch_capacity(
st.capacity().saturating_mul(8),
gr.capacity().saturating_mul(8),
);
Some(result)
})
})
}
_ => None,
}
}));
let item = match item {
Ok(item) => item,
Err(_) => Some(Err(crate::error::new_error(
crate::error::ErrorKind::Io(
std::io::Error::other("decode task panicked"),
),
))),
};
let t_send = Instant::now();
drop(tx.send((seq, item)));
PIPELINE_METRICS
.decoded_send_wait_ns
.fetch_add(elapsed_ns_u64(t_send), Relaxed);
});
}
Err(e) => {
let t_send = Instant::now();
drop(tx.send((seq, Some(Err(e)))));
PIPELINE_METRICS
.decoded_send_wait_ns
.fetch_add(elapsed_ns_u64(t_send), Relaxed);
}
}
}
});
drop(decoded_tx);
let mut pending: ReorderBuffer<Option<Result<PrimitiveBlock>>> =
ReorderBuffer::with_capacity(pipeline_config.decode_ahead);
let result: Result<()> = (|| {
loop {
let t_recv = Instant::now();
let next = decoded_rx.recv();
PIPELINE_METRICS
.decoded_recv_wait_ns
.fetch_add(elapsed_ns_u64(t_recv), Relaxed);
let (seq, item) = match next {
Ok(pair) => pair,
Err(_) => break, };
pending.push(seq, item);
PIPELINE_METRICS.record_reorder_high_water(pending.pending_len());
while let Some(item) = pending.pop_ready() {
match item {
Some(Ok(block)) => {
block_fn(block)?;
}
Some(Err(e)) => return Err(e),
None => {} }
}
}
Ok(())
})();
PIPELINE_METRICS.emit();
result
})
}