use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::task::Context;
use std::task::Poll;
use futures::FutureExt;
use futures::StreamExt;
use futures::channel::mpsc;
use futures::future;
use vortex_array::buffer::BufferHandle;
use vortex_buffer::Alignment;
use vortex_buffer::ByteBuffer;
use vortex_error::VortexResult;
use vortex_error::vortex_err;
use vortex_error::vortex_panic;
use vortex_io::VortexReadAt;
use vortex_io::runtime::Handle;
use vortex_layout::segments::SegmentFuture;
use vortex_layout::segments::SegmentId;
use vortex_layout::segments::SegmentSource;
use vortex_metrics::Counter;
use vortex_metrics::Histogram;
use vortex_metrics::Label;
use vortex_metrics::MetricBuilder;
use vortex_metrics::MetricsRegistry;
use crate::SegmentSpec;
use crate::read::IoRequestStream;
use crate::read::ReadRequest;
use crate::read::RequestId;
#[derive(Debug)]
pub enum ReadEvent {
Request(ReadRequest),
Polled(RequestId),
Dropped(RequestId),
}
pub struct FileSegmentSource {
segments: Arc<[SegmentSpec]>,
events: mpsc::UnboundedSender<ReadEvent>,
next_id: Arc<AtomicUsize>,
}
impl FileSegmentSource {
pub fn open<R: VortexReadAt + Clone>(
segments: Arc<[SegmentSpec]>,
reader: R,
handle: Handle,
metrics: RequestMetrics,
) -> Self {
let (send, recv) = mpsc::unbounded();
let max_alignment = segments
.iter()
.map(|segment| segment.alignment)
.max()
.unwrap_or_else(Alignment::none);
let coalesce_config = reader.coalesce_config().map(|mut config| {
let extra = (*max_alignment as u64).saturating_sub(1);
config.max_size = config.max_size.saturating_add(extra);
config
});
let concurrency = reader.concurrency();
if concurrency == 0 {
vortex_panic!(
"VortexReadAt::concurrency returned 0 (uri={:?}); this would stall I/O",
reader.uri()
);
}
let stream = IoRequestStream::new(
StreamExt::boxed(recv),
coalesce_config,
max_alignment,
metrics,
)
.boxed();
let drive_fut = async move {
stream
.map(move |req| {
let reader = reader.clone();
async move {
let result = reader
.read_at(req.offset(), req.len(), req.alignment())
.await;
req.resolve(result);
}
})
.buffer_unordered(concurrency)
.collect::<()>()
.await
};
handle.spawn(drive_fut).detach();
Self {
segments,
events: send,
next_id: Arc::new(AtomicUsize::new(0)),
}
}
}
impl SegmentSource for FileSegmentSource {
fn request(&self, id: SegmentId) -> SegmentFuture {
let spec = *match self.segments.get(*id as usize) {
Some(spec) => spec,
None => {
return future::ready(Err(vortex_err!("Missing segment: {}", id))).boxed();
}
};
let SegmentSpec {
offset,
length,
alignment,
} = spec;
let (send, recv) = oneshot::channel();
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let event = ReadEvent::Request(ReadRequest {
id,
offset,
length: length as usize,
alignment,
callback: send,
});
if let Err(e) = self.events.unbounded_send(event) {
return future::ready(Err(vortex_err!("Failed to submit read request: {e}"))).boxed();
}
let fut = ReadFuture {
id,
recv: recv.into_future(),
polled: false,
finished: false,
events: self.events.clone(),
};
fut.boxed()
}
}
struct ReadFuture {
id: usize,
recv: oneshot::AsyncReceiver<VortexResult<BufferHandle>>,
polled: bool,
finished: bool,
events: mpsc::UnboundedSender<ReadEvent>,
}
impl Future for ReadFuture {
type Output = VortexResult<BufferHandle>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.recv.poll_unpin(cx) {
Poll::Ready(result) => {
self.finished = true;
Poll::Ready(
result.unwrap_or_else(|e| {
Err(vortex_err!("ReadRequest dropped by runtime: {e}"))
}),
)
}
Poll::Pending if !self.polled => {
self.polled = true;
match self.events.unbounded_send(ReadEvent::Polled(self.id)) {
Ok(()) => Poll::Pending,
Err(e) => Poll::Ready(Err(vortex_err!("ReadRequest dropped by runtime: {e}"))),
}
}
_ => Poll::Pending,
}
}
}
impl Drop for ReadFuture {
fn drop(&mut self) {
if self.finished {
return;
}
drop(self.events.unbounded_send(ReadEvent::Dropped(self.id)));
}
}
pub struct RequestMetrics {
pub individual_requests: Counter,
pub coalesced_requests: Counter,
pub num_requests_coalesced: Histogram,
}
impl RequestMetrics {
pub fn new(metrics_registry: &dyn MetricsRegistry, labels: Vec<Label>) -> Self {
Self {
individual_requests: MetricBuilder::new(metrics_registry)
.add_labels(labels.clone())
.counter("io.requests.individual"),
coalesced_requests: MetricBuilder::new(metrics_registry)
.add_labels(labels.clone())
.counter("io.requests.coalesced"),
num_requests_coalesced: MetricBuilder::new(metrics_registry)
.add_labels(labels)
.histogram("io.requests.coalesced.num_coalesced"),
}
}
}
pub(crate) struct BufferSegmentSource {
buffer: ByteBuffer,
segments: Arc<[SegmentSpec]>,
}
impl BufferSegmentSource {
pub fn new(buffer: ByteBuffer, segments: Arc<[SegmentSpec]>) -> Self {
Self { buffer, segments }
}
}
impl SegmentSource for BufferSegmentSource {
fn request(&self, id: SegmentId) -> SegmentFuture {
let spec = match self.segments.get(*id as usize) {
Some(spec) => spec,
None => {
return future::ready(Err(vortex_err!("Missing segment: {}", id))).boxed();
}
};
let start = spec.offset as usize;
let end = start + spec.length as usize;
if end > self.buffer.len() {
return future::ready(Err(vortex_err!(
"Segment {} range {}..{} out of bounds for buffer of length {}",
*id,
start,
end,
self.buffer.len()
)))
.boxed();
}
let slice = self.buffer.slice(start..end).aligned(spec.alignment);
future::ready(Ok(BufferHandle::new_host(slice))).boxed()
}
}