use std::collections::VecDeque;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
use futures::FutureExt;
use futures::StreamExt;
use futures::TryFutureExt;
use futures::future::BoxFuture;
use futures::future::Shared;
use futures::stream::FuturesUnordered;
use parking_lot::Mutex;
use vortex_array::buffer::BufferHandle;
use vortex_error::VortexError;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_err;
use vortex_io::request::Completion;
use vortex_io::request::IoBatch;
use vortex_io::request::IoIntent;
use vortex_io::request::IoOwnerId;
use vortex_io::request::IoResult;
use vortex_io::request::IoService;
use vortex_io::request::IoSource;
use vortex_io::request::IoTarget;
use vortex_utils::aliases::hash_map::HashMap;
use crate::scan::execution::SegmentLocation;
use crate::segments::SegmentFuture;
use crate::segments::SegmentId;
use crate::segments::SegmentSource;
pub(super) type SegmentRanges = Arc<HashMap<(u64, usize), SegmentId>>;
pub(super) fn segment_ranges(locations: &[SegmentLocation]) -> SegmentRanges {
let ranges = locations.iter().enumerate().map(|(id, location)| {
let id = SegmentId::from(u32::try_from(id).unwrap_or(u32::MAX));
((location.offset, location.length as usize), id)
});
Arc::new(ranges.collect())
}
pub(super) struct SegmentScanIo {
segments: Arc<dyn SegmentSource>,
ranges: SegmentRanges,
}
impl SegmentScanIo {
pub(super) fn new(segments: Arc<dyn SegmentSource>, ranges: SegmentRanges) -> Self {
Self { segments, ranges }
}
}
impl IoService for SegmentScanIo {
fn session(&self) -> Arc<dyn IoSource> {
Arc::new(SegmentIoSource::new(
Arc::clone(&self.segments),
Arc::clone(&self.ranges),
))
}
}
pub(super) struct SegmentIoSource {
segments: Arc<dyn SegmentSource>,
ranges: SegmentRanges,
reads: Mutex<FuturesUnordered<BoxFuture<'static, Completion>>>,
ready: Mutex<VecDeque<Completion>>,
prefetched: Mutex<HashMap<SegmentId, Prefetched>>,
announced: Mutex<HashMap<SegmentId, VecDeque<SegmentFuture>>>,
}
type Prefetched = Shared<BoxFuture<'static, Result<BufferHandle, Arc<VortexError>>>>;
impl SegmentIoSource {
pub(super) fn new(segments: Arc<dyn SegmentSource>, ranges: SegmentRanges) -> Self {
Self {
segments,
ranges,
reads: Mutex::new(FuturesUnordered::new()),
ready: Mutex::default(),
prefetched: Mutex::default(),
announced: Mutex::default(),
}
}
}
impl IoSource for SegmentIoSource {
fn submit(&self, owner: IoOwnerId, batch: IoBatch) -> VortexResult<()> {
let reads = self.reads.lock();
let mut ready = self.ready.lock();
let mut prefetched = self.prefetched.lock();
for request in batch {
let IoTarget::Range { offset, len, .. } = request.target else {
vortex_bail!(
"SegmentIoSource only serves byte ranges, not {:?}",
request.target
);
};
let segment = *self
.ranges
.get(&(offset, len))
.ok_or_else(|| vortex_err!("No segment at bytes {offset}+{len}"))?;
if request.intent == IoIntent::Announce {
self.announced
.lock()
.entry(segment)
.or_default()
.push_back(self.segments.request(segment));
continue;
}
if request.intent == IoIntent::Prefetch {
prefetched.entry(segment).or_insert_with(|| {
let read = self
.announced
.lock()
.get_mut(&segment)
.and_then(VecDeque::pop_front)
.unwrap_or_else(|| self.segments.request(segment))
.map_err(Arc::new)
.boxed()
.shared();
drop(
read.clone()
.poll_unpin(&mut Context::from_waker(Waker::noop())),
);
read
});
continue;
}
let mut bytes = match prefetched.remove(&segment) {
Some(read) => read
.map_err(move |err| {
vortex_err!("prefetched read of segment {segment} failed: {err}")
})
.boxed(),
None => self
.announced
.lock()
.get_mut(&segment)
.and_then(VecDeque::pop_front)
.unwrap_or_else(|| self.segments.request(segment)),
};
let id = request.request;
if let Poll::Ready(result) = bytes.poll_unpin(&mut Context::from_waker(Waker::noop())) {
ready.push_back(Completion {
owner,
request: id,
result: result.map(IoResult::Bytes),
});
continue;
}
reads.push(
async move {
Completion {
owner,
request: id,
result: bytes.await.map(IoResult::Bytes),
}
}
.boxed(),
);
}
Ok(())
}
fn poll(&self) -> VortexResult<Option<Completion>> {
if let Some(completion) = self.ready.lock().pop_front() {
return Ok(Some(completion));
}
let mut cx = Context::from_waker(Waker::noop());
match self.reads.lock().poll_next_unpin(&mut cx) {
Poll::Ready(completion) => Ok(completion),
Poll::Pending => Ok(None),
}
}
fn poll_completion(&self, cx: &mut Context<'_>) -> Poll<VortexResult<Completion>> {
if let Some(completion) = self.ready.lock().pop_front() {
return Poll::Ready(Ok(completion));
}
match self.reads.lock().poll_next_unpin(cx) {
Poll::Ready(Some(completion)) => Poll::Ready(Ok(completion)),
Poll::Ready(None) => Poll::Ready(Err(vortex_err!(
"The split's driver is waiting with no reads in flight"
))),
Poll::Pending => Poll::Pending,
}
}
fn wait(&self) -> VortexResult<Completion> {
vortex_bail!("SegmentIoSource is awaited through poll_completion, not waited on")
}
fn release(&self, _owner: IoOwnerId) {}
fn clear(&self) {
self.reads.lock().clear();
self.ready.lock().clear();
self.prefetched.lock().clear();
self.announced.lock().clear();
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use futures::FutureExt;
use futures::channel::oneshot;
use futures::future;
use parking_lot::Mutex;
use vortex_array::buffer::BufferHandle;
use vortex_buffer::Alignment;
use vortex_buffer::ByteBuffer;
use vortex_error::VortexResult;
use vortex_error::vortex_err;
use vortex_io::request::IoIntent;
use vortex_io::request::IoOwnerId;
use vortex_io::request::IoRequest;
use vortex_io::request::IoRequestId;
use vortex_io::request::IoSource;
use vortex_io::request::IoTarget;
use super::SegmentIoSource;
use super::segment_ranges;
use crate::scan::execution::SegmentLocation;
use crate::segments::SegmentFuture;
use crate::segments::SegmentId;
use crate::segments::SegmentSource;
#[derive(Default)]
struct CountingSegments(AtomicUsize);
impl SegmentSource for CountingSegments {
fn request(&self, id: SegmentId) -> SegmentFuture {
self.0.fetch_add(1, Ordering::Relaxed);
let bytes = ByteBuffer::from(vec![u8::try_from(*id).unwrap_or(u8::MAX); 4]);
future::ready(Ok(BufferHandle::new_host(bytes))).boxed()
}
}
fn request(intent: IoIntent, id: u32) -> IoRequest {
IoRequest {
intent,
request: IoRequestId(id),
target: IoTarget::range(0, 4),
}
}
struct MixedSegments(Mutex<Option<oneshot::Receiver<ByteBuffer>>>);
impl SegmentSource for MixedSegments {
fn request(&self, id: SegmentId) -> SegmentFuture {
if *id == 0 {
return future::ready(Ok(BufferHandle::new_host(ByteBuffer::from(vec![0; 4]))))
.boxed();
}
let receiver = self.0.lock().take().expect("one pending read");
async move {
receiver
.await
.map(BufferHandle::new_host)
.map_err(|err| vortex_err!("{err}"))
}
.boxed()
}
}
#[tokio::test]
async fn pending_read_wakes_after_an_immediate_completion() -> VortexResult<()> {
let (sender, receiver) = oneshot::channel();
let segments = Arc::new(MixedSegments(Mutex::new(Some(receiver))));
let locations = [0, 4].map(|offset| SegmentLocation {
offset,
length: 4,
alignment: Alignment::none(),
});
let io = SegmentIoSource::new(segments, segment_ranges(&locations));
let mut pending = request(IoIntent::Fetch, 1);
pending.target = IoTarget::range(4, 4);
io.submit(IoOwnerId(0), vec![pending, request(IoIntent::Fetch, 0)])?;
let immediate = io.poll()?.expect("the in-memory read is already ready");
assert_eq!(immediate.request, IoRequestId(0));
assert!(immediate.result.is_ok());
assert!(io.poll()?.is_none());
let delivery = tokio::spawn(async move {
tokio::task::yield_now().await;
sender
.send(ByteBuffer::from(vec![1; 4]))
.expect("read is live");
});
let completed = std::future::poll_fn(|cx| io.poll_completion(cx)).await?;
assert_eq!(completed.request, IoRequestId(1));
assert!(completed.result.is_ok());
delivery.await.expect("delivery task succeeds");
assert!(io.poll()?.is_none());
Ok(())
}
#[tokio::test]
async fn fetch_reuses_a_prefetched_read() -> VortexResult<()> {
let segments = Arc::new(CountingSegments::default());
let location = SegmentLocation {
offset: 0,
length: 4,
alignment: Alignment::none(),
};
let io = SegmentIoSource::new(Arc::clone(&segments) as _, segment_ranges(&[location]));
io.submit(IoOwnerId(0), vec![request(IoIntent::Prefetch, 0)])?;
assert_eq!(segments.0.load(Ordering::Relaxed), 1);
io.submit(IoOwnerId(0), vec![request(IoIntent::Fetch, 1)])?;
assert_eq!(segments.0.load(Ordering::Relaxed), 1);
let completion = std::future::poll_fn(|cx| io.poll_completion(cx)).await?;
assert_eq!(completion.request, IoRequestId(1));
assert!(completion.result.is_ok());
assert!(io.poll()?.is_none());
io.submit(IoOwnerId(0), vec![request(IoIntent::Fetch, 2)])?;
let completion = std::future::poll_fn(|cx| io.poll_completion(cx)).await?;
assert_eq!(completion.request, IoRequestId(2));
assert!(completion.result.is_ok());
assert_eq!(segments.0.load(Ordering::Relaxed), 2);
Ok(())
}
#[tokio::test]
async fn announcements_keep_independent_consumers() -> VortexResult<()> {
let segments = Arc::new(CountingSegments::default());
let location = SegmentLocation {
offset: 0,
length: 4,
alignment: Alignment::none(),
};
let io = SegmentIoSource::new(Arc::clone(&segments) as _, segment_ranges(&[location]));
io.submit(
IoOwnerId(0),
vec![
request(IoIntent::Announce, 0),
request(IoIntent::Announce, 1),
],
)?;
assert_eq!(segments.0.load(Ordering::Relaxed), 2);
for id in 2..5 {
io.submit(IoOwnerId(0), vec![request(IoIntent::Fetch, id)])?;
assert!(
std::future::poll_fn(|cx| io.poll_completion(cx))
.await?
.result
.is_ok()
);
assert_eq!(
segments.0.load(Ordering::Relaxed),
if id < 4 { 2 } else { 3 }
);
}
Ok(())
}
}