Skip to main content

vortex_io/request/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! IO requests, results, and the source that fulfils them.
5//!
6//! The protocol moves bytes at a range. A source has exactly one other property a consumer may
7//! ask for, its length, because a Vortex file cannot be located from its start without it.
8//! Nothing else is a request: anything a stage needs that is not bytes at a range is
9//! construction-time data for that stage.
10
11use std::sync::Arc;
12use std::task::Context;
13use std::task::Poll;
14
15use vortex_array::buffer::BufferHandle;
16use vortex_buffer::Alignment;
17use vortex_error::VortexResult;
18use vortex_error::vortex_panic;
19
20mod source;
21
22pub use source::ReadAtIoSource;
23
24/// Consumer-chosen label for one request, unique within the issuing object.
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
26pub struct IoRequestId(pub u32);
27
28/// What to fetch from the source: its length, or bytes at a range. This enum is closed; see the
29/// module documentation.
30#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
31pub enum IoTarget {
32    /// Total length of the source in bytes.
33    Size,
34    /// An absolute byte range.
35    Range {
36        /// Absolute byte offset of the first byte.
37        offset: u64,
38        /// Number of bytes to read.
39        len: usize,
40        /// The alignment the delivered bytes have at least.
41        alignment: Alignment,
42    },
43}
44
45impl IoTarget {
46    /// A byte range whose bytes need no particular alignment.
47    pub fn range(offset: u64, len: usize) -> Self {
48        Self::Range {
49            offset,
50            len,
51            alignment: Alignment::none(),
52        }
53    }
54}
55
56/// A stage's reason for registering bytes. Optional hints may be declined by the service.
57#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
58pub enum IoIntent {
59    /// Required bytes, delivered to the requesting object.
60    Fetch,
61    /// Optional bytes eligible for an independent background read.
62    Prefetch,
63    /// Coalescing interest only; never starts an independent read.
64    Announce,
65}
66
67/// One request: a consumer-chosen id and the target it names.
68#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
69pub struct IoRequest {
70    /// Required delivery, background prefetch, or coalescing-only interest.
71    pub intent: IoIntent,
72    /// Identifies the request within the issuing object.
73    pub request: IoRequestId,
74    /// What to fetch.
75    pub target: IoTarget,
76}
77
78/// Requests an object publishes together, each exactly once.
79pub type IoBatch = Vec<IoRequest>;
80
81/// Result for one request. The variant matches the target: `Size` for `Size`, `Bytes` for
82/// `Range`. The driver checks this before delivering, so a consumer never sees a mismatch.
83#[derive(Debug, Clone)]
84pub enum IoResult {
85    /// Total length of the source in bytes.
86    Size(u64),
87    /// The bytes of a range request, possibly not yet resident on the host.
88    Bytes(BufferHandle),
89}
90
91impl IoResult {
92    /// Whether this is the variant `target` must be answered with.
93    pub fn matches(&self, target: &IoTarget) -> bool {
94        matches!(
95            (self, target),
96            (Self::Size(_), IoTarget::Size) | (Self::Bytes(_), IoTarget::Range { .. })
97        )
98    }
99
100    /// The variant name, for messages.
101    pub fn kind(&self) -> &'static str {
102        match self {
103            Self::Size(_) => "size",
104            Self::Bytes(_) => "bytes",
105        }
106    }
107}
108
109/// Opaque owner identity chosen by the caller, unique within a registration scope.
110#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
111pub struct IoOwnerId(pub u64);
112
113/// One finished request: its owner, its request identity, and the answer.
114pub struct Completion {
115    /// The owner that submitted the request.
116    pub owner: IoOwnerId,
117    /// The request's consumer-chosen id.
118    pub request: IoRequestId,
119    /// The answer, or the read error, which fails the registration scope.
120    pub result: VortexResult<IoResult>,
121}
122
123/// IO registration and dispatch for one registration scope, such as the work descended from one
124/// root planner. Services may decline optional hints. Accepted optional work belongs to the
125/// source, not to the publishing stage, and lives until the source is cleared or dropped.
126pub trait IoSource: Send + Sync {
127    /// Registers a whole batch before any of its reads become eligible for dispatch.
128    /// Registration does not perform IO and never blocks.
129    fn submit(&self, owner: IoOwnerId, batch: IoBatch) -> VortexResult<()>;
130
131    /// Advances eligible IO without blocking and returns a required completion if ready.
132    /// Optional successes stay in the source; any read failure fails the run.
133    fn poll(&self) -> VortexResult<Option<Completion>>;
134
135    /// Like [`poll`](Self::poll), registering `cx` to be woken when a required completion is
136    /// ready. Fails when no fetch is in flight, since nothing would ever wake the caller.
137    fn poll_completion(&self, cx: &mut Context<'_>) -> Poll<VortexResult<Completion>>;
138
139    /// Blocks until a required completion is available or a read fails.
140    fn wait(&self) -> VortexResult<Completion>;
141
142    /// Releases a retired object's fetch subscriptions, preserving optional announcements.
143    fn release(&self, owner: IoOwnerId);
144
145    /// Cancels the scope's registrations and drops retained bytes and pending completions.
146    fn clear(&self);
147}
148
149/// A byte source's IO service, shared by everything that reads the source.
150///
151/// Each registration scope reads through its own [`IoSource`], and the service is free to share
152/// and coalesce the reads of all of them.
153pub trait IoService: Send + Sync {
154    /// A fresh source for one registration scope.
155    fn session(&self) -> Arc<dyn IoSource>;
156}
157
158/// Receives fulfilled requests. Implementations only store; they never compute here.
159pub trait IoConsumer {
160    /// Stores the result for `request`.
161    ///
162    /// The driver only delivers fetches the consumer published and results that match their
163    /// targets, so anything else is a driver bug and the consumer may panic.
164    fn set_io_result(&mut self, request: IoRequestId, result: IoResult);
165}
166
167/// One outstanding request at a time, with ids that never repeat within the owner.
168///
169/// A consumer that issues requests one after another keeps its id allocation, its in-flight
170/// request, and the delivered result here, so its own state carries none of them.
171#[derive(Default)]
172pub struct IoSlot {
173    next_id: u32,
174    request: Option<IoRequest>,
175    result: Option<IoResult>,
176}
177
178impl IoSlot {
179    /// Issues a request for `target` with a fresh id. Panics if one is already outstanding or
180    /// its result has not been taken, since that is a bug in the owner.
181    pub fn issue(&mut self, target: IoTarget) -> IoBatch {
182        if self.request.is_some() || self.result.is_some() {
183            vortex_panic!("IoSlot: issue while a request is outstanding");
184        }
185        let request = IoRequest {
186            intent: IoIntent::Fetch,
187            request: IoRequestId(self.next_id),
188            target,
189        };
190        self.next_id += 1;
191        self.request = Some(request);
192        vec![request]
193    }
194
195    /// Whether the issued request is still undelivered.
196    pub fn is_waiting(&self) -> bool {
197        self.request.is_some()
198    }
199
200    /// Stores the result for the in-flight request. Panics on an unknown id or a result that
201    /// does not match the target, since the driver guarantees both.
202    pub fn deliver(&mut self, id: IoRequestId, result: IoResult) {
203        let Some(request) = self.request.take_if(|request| request.request == id) else {
204            vortex_panic!("IoSlot: delivery of {id:?}, which is not outstanding");
205        };
206        if !result.matches(&request.target) {
207            vortex_panic!(
208                "IoSlot: {id:?} for {:?} answered with {}",
209                request.target,
210                result.kind()
211            );
212        }
213        self.result = Some(result);
214    }
215
216    /// The delivered result, once.
217    pub fn take(&mut self) -> Option<IoResult> {
218        self.result.take()
219    }
220}
221
222#[cfg(test)]
223mod tests;