Skip to main content

tantivy_remote/
file.rs

1use std::{
2    fmt::{self, Debug, Formatter},
3    io,
4    ops::Range,
5    sync::Arc,
6};
7
8use async_trait::async_trait;
9use block_on_place::HandleExt;
10use tantivy::{
11    HasLen,
12    directory::{FileHandle, OwnedBytes},
13};
14use tokio::runtime::Handle;
15
16use crate::{context::Context, operator::Operator};
17
18/// A [`FileHandle`] implementation for remote files, with automatic caching.
19///
20/// A file's bytes come from one of:
21/// - the object store, as the whole object ([`File::open`]);
22/// - the object store, as a byte range of a [bundle][1] object ([`File::bundled`]);
23/// - memory, for files that are [logically empty][2] or whose bundle has not been synced
24///   yet.
25///
26/// [1]: crate::bundle
27/// [2]: crate::empty
28#[derive(Clone)]
29pub struct File {
30    /// The path of the object this reads from (the file's own object, or the bundle
31    /// object it lives in).
32    path: String,
33
34    /// Where the file's bytes come from.
35    backend: Backend,
36}
37
38/// The source of a [`File`]'s bytes.
39#[derive(Clone)]
40enum Backend {
41    /// The bytes live in the object storage and are read on demand, as the byte range
42    /// `[offset, offset + len)` of the object at [`File::path`].
43    ///
44    /// For a standalone file `offset` is `0` and `len` is the whole object. For a
45    /// bundled file they locate the component inside its bundle object.
46    Remote {
47        rt: Handle,
48
49        /// The storage backend the object this is reading is located in.
50        operator: Operator,
51
52        /// The offset of the file's bytes within the object.
53        offset: u64,
54
55        /// The length of the file's bytes.
56        len: u64,
57
58        /// Defines the size of the chunks which should be read from the storage backend.
59        chunks: Option<usize>,
60
61        /// Defines the number of concurrent requests to make when reading a file from
62        /// the storage backend.
63        concurrency: Option<usize>,
64    },
65
66    /// The bytes are kept in memory and served directly, without any remote read.
67    ///
68    /// Used for [logically empty][1] files and for [bundled][2] files that have not yet
69    /// been synced to the backing store.
70    ///
71    /// [1]: crate::empty
72    /// [2]: crate::bundle
73    Memory { bytes: OwnedBytes },
74}
75
76impl File {
77    /// Opens the remote file at `path` for reading the whole object, returning a
78    /// [`File`] handle for it.
79    ///
80    /// `len` is the object's length (from its metadata).
81    pub(crate) fn open(
82        path: impl Into<String>,
83        len: u64,
84        rt: Handle,
85        operator: Operator,
86        context: &Context,
87    ) -> Arc<Self> {
88        Arc::new(Self {
89            path: path.into(),
90            backend: Backend::Remote {
91                rt,
92                operator,
93                offset: 0,
94                len,
95                chunks: context.read_chunks,
96                concurrency: context.read_concurrency,
97            },
98        })
99    }
100
101    /// Opens a [bundled][1] file: the byte range `[offset, offset + len)` of the bundle
102    /// object at `path`.
103    ///
104    /// [1]: crate::bundle
105    pub(crate) fn bundled(
106        path: impl Into<String>,
107        offset: u64,
108        len: u64,
109        rt: Handle,
110        operator: Operator,
111        context: &Context,
112    ) -> Arc<Self> {
113        Arc::new(Self {
114            path: path.into(),
115            backend: Backend::Remote {
116                rt,
117                operator,
118                offset,
119                len,
120                chunks: context.read_chunks,
121                concurrency: context.read_concurrency,
122            },
123        })
124    }
125
126    /// Builds an in-memory handle for a [logically empty][1] file, serving the static
127    /// `bytes` directly instead of reading anything from the storage backend.
128    ///
129    /// [1]: crate::empty
130    pub(crate) fn memory(path: impl Into<String>, bytes: &'static [u8]) -> Arc<Self> {
131        Arc::new(Self {
132            path: path.into(),
133            backend: Backend::Memory {
134                bytes: OwnedBytes::new(bytes),
135            },
136        })
137    }
138
139    /// Builds an in-memory handle serving owned `bytes` directly.
140    ///
141    /// Used for a [bundled][1] file whose bundle object has not been synced yet.
142    ///
143    /// [1]: crate::bundle
144    pub(crate) fn memory_owned(path: impl Into<String>, bytes: Vec<u8>) -> Arc<Self> {
145        Arc::new(Self {
146            path: path.into(),
147            backend: Backend::Memory {
148                bytes: OwnedBytes::new(bytes),
149            },
150        })
151    }
152
153    /// Returns the path of the object this reads from.
154    #[inline]
155    pub fn path(&self) -> &str {
156        &self.path
157    }
158}
159
160#[async_trait]
161impl FileHandle for File {
162    fn read_bytes(&self, range: Range<usize>) -> io::Result<OwnedBytes> {
163        match &self.backend {
164            Backend::Remote { rt, .. } => rt.block_on_place(self.read_bytes_async(range)),
165            Backend::Memory { bytes } => Ok(bytes.slice(range)),
166        }
167    }
168
169    async fn read_bytes_async(&self, range: Range<usize>) -> io::Result<OwnedBytes> {
170        let (operator, offset, chunks, concurrency) = match &self.backend {
171            Backend::Remote {
172                operator,
173                offset,
174                chunks,
175                concurrency,
176                ..
177            } => (operator, *offset, chunks, concurrency),
178
179            // Memory-backed files never touch the network.
180            Backend::Memory { bytes } => return Ok(bytes.slice(range)),
181        };
182
183        let mut reader = operator.reader_with(&self.path);
184        if let Some(chunks) = chunks {
185            reader = reader.chunk(*chunks);
186        }
187
188        if let Some(concurrency) = concurrency {
189            reader = reader.concurrent(*concurrency);
190        }
191
192        let reader = reader.await.map_err(io::Error::other)?;
193        // Shift the requested range by the file's offset within the object so that a
194        // bundled file only ever reads its own slice.
195        let range = Range {
196            start: offset + range.start as u64,
197            end: offset + range.end as u64,
198        };
199
200        let buffer = reader.read(range).await.map_err(io::Error::other)?;
201        // TODO(MLB): avoid copying
202        let bytes = buffer.to_vec();
203        let bytes = OwnedBytes::new(bytes);
204
205        Ok(bytes)
206    }
207}
208
209impl HasLen for File {
210    fn len(&self) -> usize {
211        match &self.backend {
212            Backend::Remote { len, .. } => *len as usize,
213            Backend::Memory { bytes } => bytes.len(),
214        }
215    }
216}
217
218impl Debug for File {
219    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
220        let mut f = f.debug_struct("File");
221        f.field("path", &self.path);
222
223        match &self.backend {
224            Backend::Remote { offset, len, .. } => f.field("offset", offset).field("len", len),
225            Backend::Memory { bytes } => f.field("memory_len", &bytes.len()),
226        };
227
228        f.finish()
229    }
230}
231
232/// A [`FileHandle`] exposing the byte range `[offset, offset + len)` of an inner
233/// handle.
234///
235/// Used by [`LightDirectory`][1] to present one [bundled][2] component as a slice
236/// of the inner directory's bundle-object handle.
237///
238/// [1]: crate::LightDirectory
239/// [2]: crate::bundle
240pub(crate) struct Slice {
241    inner: Arc<dyn FileHandle>,
242    offset: usize,
243    len: usize,
244}
245
246impl Slice {
247    /// Wraps `inner`, exposing only `[offset, offset + len)`.
248    pub fn new(inner: Arc<dyn FileHandle>, offset: usize, len: usize) -> Arc<Self> {
249        Arc::new(Self { inner, offset, len })
250    }
251}
252
253#[async_trait]
254impl FileHandle for Slice {
255    fn read_bytes(&self, range: Range<usize>) -> io::Result<OwnedBytes> {
256        self.inner
257            .read_bytes(self.offset + range.start..self.offset + range.end)
258    }
259
260    async fn read_bytes_async(&self, range: Range<usize>) -> io::Result<OwnedBytes> {
261        self.inner
262            .read_bytes_async(self.offset + range.start..self.offset + range.end)
263            .await
264    }
265}
266
267impl HasLen for Slice {
268    fn len(&self) -> usize {
269        self.len
270    }
271}
272
273impl Debug for Slice {
274    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
275        f.debug_struct("Slice")
276            .field("offset", &self.offset)
277            .field("len", &self.len)
278            .finish()
279    }
280}