1use std::fs::File;
5use std::ops::Range;
6use std::sync::Arc;
7
8use crate::local::join_local_io;
9#[cfg(windows)]
10use crate::local::read_exact_at;
11#[cfg(unix)]
12use std::os::unix::fs::FileExt;
13
14use bytes::Bytes;
15use futures::{
16 FutureExt,
17 future::{BoxFuture, Shared},
18 stream::{self, StreamExt},
19};
20use lance_core::deepsize::DeepSizeOf;
21use lance_core::{Error, Result, error::CloneableError};
22use object_store::ObjectStoreExt;
23use object_store::{GetOptions, GetResult, ObjectStore, Result as OSResult, path::Path};
24use tokio::sync::OnceCell;
25use tracing::instrument;
26
27use crate::{
28 object_store::DEFAULT_CLOUD_IO_PARALLELISM,
29 traits::{ByteStream, Reader},
30};
31
32trait StaticGetRange {
33 fn path(&self) -> &Path;
34 fn get_range(&self) -> BoxFuture<'static, OSResult<GetResult>>;
35}
36
37struct GetRequest {
40 object_store: Arc<dyn ObjectStore>,
41 path: Path,
42 options: GetOptions,
43}
44
45impl StaticGetRange for Arc<GetRequest> {
46 fn path(&self) -> &Path {
47 &self.path
48 }
49
50 fn get_range(&self) -> BoxFuture<'static, OSResult<GetResult>> {
51 let store_and_path = self.clone();
52 Box::pin(async move {
53 store_and_path
54 .object_store
55 .get_opts(&store_and_path.path, store_and_path.options.clone())
56 .await
57 })
58 }
59}
60
61#[derive(Debug)]
65pub struct CloudObjectReader {
66 pub object_store: Arc<dyn ObjectStore>,
68 pub path: Path,
70 size: OnceCell<usize>,
72
73 block_size: usize,
74 io_parallelism: usize,
75 download_retry_count: usize,
76}
77
78impl DeepSizeOf for CloudObjectReader {
79 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
80 self.path.as_ref().deep_size_of_children(context)
82 }
83}
84
85impl CloudObjectReader {
86 pub fn new(
88 object_store: Arc<dyn ObjectStore>,
89 path: Path,
90 block_size: usize,
91 known_size: Option<usize>,
92 download_retry_count: usize,
93 ) -> Result<Self> {
94 Ok(Self {
95 object_store,
96 path,
97 size: OnceCell::new_with(known_size),
98 block_size,
99 io_parallelism: DEFAULT_CLOUD_IO_PARALLELISM,
100 download_retry_count,
101 })
102 }
103
104 pub fn with_io_parallelism(mut self, io_parallelism: usize) -> Self {
111 self.io_parallelism = io_parallelism;
112 self
113 }
114}
115
116async fn do_with_retry<'a, O>(f: impl Fn() -> BoxFuture<'a, OSResult<O>> + Clone) -> OSResult<O> {
120 let mut retries = 3;
121 loop {
122 let f = f.clone();
123 match f().await {
124 Ok(val) => return Ok(val),
125 Err(err) => {
126 if retries == 0 {
127 return Err(err);
128 }
129 retries -= 1;
130 }
131 }
132 }
133}
134
135async fn do_get_with_outer_retry(
142 download_retry_count: usize,
143 get_request: Arc<GetRequest>,
144 desc: impl Fn() -> String,
145) -> OSResult<Bytes> {
146 let mut retries = download_retry_count;
147 loop {
148 let get_request_clone = get_request.clone();
149 let get_result = do_with_retry(move || get_request_clone.get_range()).await?;
150 match get_result.bytes().await {
151 Ok(bytes) => return Ok(bytes),
152 Err(err) => {
153 if retries == 0 {
154 log::warn!(
155 "Failed to download {} from {} after {} attempts. This may indicate that cloud storage is overloaded or your timeout settings are too restrictive. Error details: {:?}",
156 desc(),
157 get_request.path(),
158 download_retry_count,
159 err
160 );
161 return Err(err);
162 }
163 log::debug!(
164 "Retrying {} from {} (remaining retries: {}). Error details: {:?}",
165 desc(),
166 get_request.path(),
167 retries,
168 err
169 );
170 retries -= 1;
171 }
172 }
173 }
174}
175
176impl Reader for CloudObjectReader {
177 fn path(&self) -> &Path {
178 &self.path
179 }
180
181 fn block_size(&self) -> usize {
182 self.block_size
183 }
184
185 fn io_parallelism(&self) -> usize {
186 self.io_parallelism
187 }
188
189 fn size(&self) -> BoxFuture<'_, object_store::Result<usize>> {
191 Box::pin(async move {
192 self.size
193 .get_or_try_init(|| async move {
194 let meta =
195 do_with_retry(|| Box::pin(self.object_store.head(&self.path))).await?;
196 Ok(meta.size as usize)
197 })
198 .await
199 .cloned()
200 })
201 }
202
203 #[instrument(level = "debug", skip(self))]
204 fn get_range(&self, range: Range<usize>) -> BoxFuture<'static, OSResult<Bytes>> {
205 let object_store = self.object_store.clone();
206 let path = self.path.clone();
207 let range = Range {
208 start: range.start as u64,
209 end: range.end as u64,
210 };
211 Box::pin(async move {
212 do_with_retry(|| Box::pin(object_store.get_range(&path, range.clone()))).await
213 })
214 }
215
216 #[instrument(level = "debug", skip_all)]
217 fn get_all(&self) -> BoxFuture<'_, OSResult<Bytes>> {
218 let get_request = Arc::new(GetRequest {
219 object_store: self.object_store.clone(),
220 path: self.path.clone(),
221 options: GetOptions::default(),
222 });
223 Box::pin(async move {
224 do_get_with_outer_retry(self.download_retry_count, get_request, || {
225 "read_all".to_string()
226 })
227 .await
228 })
229 }
230
231 fn get_stream(&self) -> BoxFuture<'_, OSResult<ByteStream>> {
232 let get_request = Arc::new(GetRequest {
233 object_store: self.object_store.clone(),
234 path: self.path.clone(),
235 options: GetOptions::default(),
236 });
237 Box::pin(async move {
238 let get_request_clone = get_request.clone();
239 let get_result = do_with_retry(move || get_request_clone.get_range()).await?;
240 Ok(get_result.into_stream())
241 })
242 }
243
244 fn get_range_stream(&self, range: Range<usize>) -> BoxFuture<'_, OSResult<ByteStream>> {
245 let get_request = Arc::new(GetRequest {
246 object_store: self.object_store.clone(),
247 path: self.path.clone(),
248 options: GetOptions {
249 range: Some(
250 Range {
251 start: range.start as u64,
252 end: range.end as u64,
253 }
254 .into(),
255 ),
256 ..Default::default()
257 },
258 });
259 Box::pin(async move {
260 let get_request_clone = get_request.clone();
261 let get_result = do_with_retry(move || get_request_clone.get_range()).await?;
262 Ok(get_result.into_stream())
263 })
264 }
265}
266
267#[derive(Debug)]
268pub struct SmallReaderInner {
269 path: Path,
270 size: usize,
271 state: std::sync::Mutex<SmallReaderState>,
272}
273
274#[derive(Clone, Debug)]
282pub struct SmallReader {
283 inner: Arc<SmallReaderInner>,
284}
285
286enum SmallReaderState {
287 Loading(Shared<BoxFuture<'static, std::result::Result<Bytes, CloneableError>>>),
288 Finished(std::result::Result<Bytes, CloneableError>),
289}
290
291impl std::fmt::Debug for SmallReaderState {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 match self {
294 Self::Loading(_) => write!(f, "Loading"),
295 Self::Finished(Ok(data)) => {
296 write!(f, "Finished({} bytes)", data.len())
297 }
298 Self::Finished(Err(err)) => {
299 write!(f, "Finished({})", err.0)
300 }
301 }
302 }
303}
304
305impl SmallReader {
306 pub fn new(
307 store: Arc<dyn ObjectStore>,
308 path: Path,
309 download_retry_count: usize,
310 size: usize,
311 ) -> Self {
312 let path_ref = path.clone();
313 let state = SmallReaderState::Loading(
314 Box::pin(async move {
315 let object_reader =
316 CloudObjectReader::new(store, path_ref, 0, None, download_retry_count)
317 .map_err(CloneableError)?;
318 object_reader
319 .get_all()
320 .await
321 .map_err(|err| CloneableError(Error::from(err)))
322 })
323 .boxed()
324 .shared(),
325 );
326 Self {
327 inner: Arc::new(SmallReaderInner {
328 path,
329 size,
330 state: std::sync::Mutex::new(state),
331 }),
332 }
333 }
334}
335
336impl SmallReaderInner {
337 async fn wait(&self) -> OSResult<Bytes> {
338 let future = {
339 let state = self.state.lock().unwrap();
340 match &*state {
341 SmallReaderState::Loading(future) => future.clone(),
342 SmallReaderState::Finished(result) => {
343 return result.clone().map_err(|err| err.0.into());
344 }
345 }
346 };
347
348 let result = future.await;
349 let result_to_return = result.clone().map_err(|err| err.0.into());
350 let mut state = self.state.lock().unwrap();
351 if matches!(*state, SmallReaderState::Loading(_)) {
352 *state = SmallReaderState::Finished(result);
353 }
354 result_to_return
355 }
356}
357
358impl Reader for SmallReader {
359 fn path(&self) -> &Path {
360 &self.inner.path
361 }
362
363 fn block_size(&self) -> usize {
364 64 * 1024
365 }
366
367 fn io_parallelism(&self) -> usize {
368 1024
369 }
370
371 fn size(&self) -> BoxFuture<'_, OSResult<usize>> {
373 let size = self.inner.size;
374 Box::pin(async move { Ok(size) })
375 }
376
377 fn get_range(&self, range: Range<usize>) -> BoxFuture<'static, OSResult<Bytes>> {
378 let inner = self.inner.clone();
379 Box::pin(async move {
380 let bytes = inner.wait().await?;
381 let start = range.start;
382 let end = range.end;
383 if start >= bytes.len() || end > bytes.len() {
384 return Err(object_store::Error::Generic {
385 store: "memory",
386 source: format!(
387 "Invalid range {}..{} for object of size {} bytes",
388 start,
389 end,
390 bytes.len()
391 )
392 .into(),
393 });
394 }
395 Ok(bytes.slice(range))
396 })
397 }
398
399 fn get_all(&self) -> BoxFuture<'_, OSResult<Bytes>> {
400 Box::pin(async move { self.inner.wait().await })
401 }
402}
403
404pub(crate) fn stream_local_range(
405 file: Arc<File>,
406 path: Path,
407 io_tracker: Arc<crate::utils::tracking_store::IOTracker>,
408 range: Range<usize>,
409 chunk_size: usize,
410) -> ByteStream {
411 stream::try_unfold(
412 (file, path, io_tracker, range.start, range.end),
413 move |state| async move {
414 let (file, path, io_tracker, start, end) = state;
415 if start >= end {
416 return Ok(None);
417 }
418
419 let next = (start + chunk_size).min(end);
420 let file_clone = file.clone();
421 let path_clone = path.clone();
422 let num_bytes = (next - start) as u64;
423 let metrics = io_tracker.begin_io("get");
424 let result = join_local_io(tokio::task::spawn_blocking(move || {
425 let mut buf = bytes::BytesMut::with_capacity(next - start);
426 unsafe { buf.set_len(next - start) };
428 #[cfg(unix)]
429 file_clone.read_exact_at(buf.as_mut(), start as u64)?;
430 #[cfg(windows)]
431 read_exact_at(file_clone, buf.as_mut(), start as u64)?;
432 Ok::<_, std::io::Error>(buf.freeze())
433 }))
434 .await;
435 metrics.record(&result, num_bytes);
436 let bytes = result?;
437
438 io_tracker.record_read(
439 "get_range_stream",
440 path_clone,
441 num_bytes,
442 Some(start as u64..next as u64),
443 );
444
445 Ok(Some((bytes, (file, path, io_tracker, next, end))))
446 },
447 )
448 .boxed()
449}
450
451impl DeepSizeOf for SmallReader {
452 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
453 let mut size = self.inner.path.as_ref().deep_size_of_children(context);
454
455 if let Ok(guard) = self.inner.state.try_lock()
456 && let SmallReaderState::Finished(Ok(data)) = &*guard
457 {
458 size += data.len();
459 }
460
461 size
462 }
463}