Skip to content

Commit c42934b

Browse files
authored
Remove mutable state from Vortex open options (#8879)
## Rationale for this change Vortex open options shouldn't have mutable state, so return the mutable state separately --------- Signed-off-by: Onur Satici <onur@spiraldb.com>
1 parent ba4acb6 commit c42934b

2 files changed

Lines changed: 37 additions & 46 deletions

File tree

vortex-file/src/open.rs

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use std::sync::Arc;
55

66
use futures::executor::block_on;
7-
use parking_lot::RwLock;
87
use vortex_array::dtype::DType;
98
use vortex_array::memory::MemorySessionExt;
109
use vortex_array::session::ArraySessionExt;
@@ -41,13 +40,19 @@ use crate::segments::RequestMetrics;
4140

4241
const INITIAL_READ_SIZE: usize = MAX_POSTSCRIPT_SIZE as usize + EOF_SIZE;
4342

43+
struct FooterRead {
44+
footer: Footer,
45+
initial_segments: HashMap<SegmentId, ByteBuffer>,
46+
}
47+
4448
/// Open options for a Vortex file reader.
4549
///
4650
/// Options are session-bound because opening a file may need array, layout, dtype, runtime, and
4751
/// memory registries. Construct with [`OpenOptionsSessionExt::open_options`] for the common path.
4852
///
4953
/// The opener first resolves a [`Footer`], then creates a segment source for later scans. Known
5054
/// file metadata can be supplied up front to avoid IO during footer discovery.
55+
#[derive(Clone)]
5156
pub struct VortexOpenOptions {
5257
/// The session to use for opening the file.
5358
session: VortexSession,
@@ -61,8 +66,6 @@ pub struct VortexOpenOptions {
6166
dtype: Option<DType>,
6267
/// An optional, externally provided, file layout.
6368
footer: Option<Footer>,
64-
/// The segments read during the initial read.
65-
initial_read_segments: RwLock<HashMap<SegmentId, ByteBuffer>>,
6669
/// A metrics registry for the file.
6770
metrics_registry: Option<Arc<dyn MetricsRegistry>>,
6871
/// Default labels applied to all the file's metrics
@@ -71,23 +74,6 @@ pub struct VortexOpenOptions {
7174
cache_layout_reader: bool,
7275
}
7376

74-
impl Clone for VortexOpenOptions {
75-
fn clone(&self) -> Self {
76-
Self {
77-
session: self.session.clone(),
78-
segment_cache: self.segment_cache.clone(),
79-
initial_read_size: self.initial_read_size,
80-
file_size: self.file_size,
81-
dtype: self.dtype.clone(),
82-
footer: self.footer.clone(),
83-
initial_read_segments: RwLock::new(self.initial_read_segments.read().clone()),
84-
metrics_registry: self.metrics_registry.clone(),
85-
labels: self.labels.clone(),
86-
cache_layout_reader: self.cache_layout_reader,
87-
}
88-
}
89-
}
90-
9177
/// Extension trait for constructing [`VortexOpenOptions`] from a session.
9278
pub trait OpenOptionsSessionExt:
9379
ArraySessionExt + LayoutSessionExt + RuntimeSessionExt + MemorySessionExt
@@ -101,7 +87,6 @@ pub trait OpenOptionsSessionExt:
10187
file_size: None,
10288
dtype: None,
10389
footer: None,
104-
initial_read_segments: Default::default(),
10590
metrics_registry: None,
10691
labels: Vec::default(),
10792
cache_layout_reader: false,
@@ -247,7 +232,7 @@ impl VortexOpenOptions {
247232

248233
let footer = match opts.footer.take() {
249234
Some(footer) => footer,
250-
None => block_on(opts.read_footer(&buffer))?,
235+
None => block_on(opts.read_footer(&buffer))?.footer,
251236
};
252237
footer.validate_file_size(buffer.len() as u64)?;
253238

@@ -277,18 +262,24 @@ impl VortexOpenOptions {
277262
.clone()
278263
.unwrap_or_else(|| Arc::new(DefaultMetricsRegistry::default()));
279264

280-
let footer = if let Some(footer) = self.footer {
265+
let FooterRead {
266+
footer,
267+
initial_segments,
268+
} = if let Some(footer) = self.footer {
281269
if let Some(file_size) = self.file_size {
282270
footer.validate_file_size(file_size)?;
283271
}
284-
footer
272+
FooterRead {
273+
footer,
274+
initial_segments: HashMap::default(),
275+
}
285276
} else {
286277
self.read_footer(&reader).await?
287278
};
288279

289280
let segment_cache = Arc::new(InstrumentedSegmentCache::new(
290281
InitialReadSegmentCache {
291-
initial: self.initial_read_segments,
282+
initial: initial_segments,
292283
fallback: segment_cache,
293284
},
294285
metrics_registry.as_ref(),
@@ -319,7 +310,7 @@ impl VortexOpenOptions {
319310
})
320311
}
321312

322-
async fn read_footer(&self, read: &dyn VortexReadAt) -> VortexResult<Footer> {
313+
async fn read_footer(&self, read: &dyn VortexReadAt) -> VortexResult<FooterRead> {
323314
// Fetch the file size and perform the initial read.
324315
let file_size = match self.file_size {
325316
None => read.size().await?,
@@ -367,23 +358,26 @@ impl VortexOpenOptions {
367358
// If the initial read happened to cover any segments, then we can populate the
368359
// segment cache
369360
let initial_offset = file_size - (deserializer.buffer().len() as u64);
370-
self.populate_initial_segments(initial_offset, deserializer.buffer(), &footer)?;
361+
let initial_segments =
362+
Self::collect_initial_segments(initial_offset, deserializer.buffer(), &footer)?;
371363

372-
Ok(footer)
364+
Ok(FooterRead {
365+
footer,
366+
initial_segments,
367+
})
373368
}
374369

375-
/// Populate segments in the cache that were covered by the initial read.
376-
fn populate_initial_segments(
377-
&self,
370+
/// Collect segments that were covered by the initial read.
371+
fn collect_initial_segments(
378372
initial_offset: u64,
379373
initial_read: &ByteBuffer,
380374
footer: &Footer,
381-
) -> VortexResult<()> {
375+
) -> VortexResult<HashMap<SegmentId, ByteBuffer>> {
382376
let first_idx = footer
383377
.segment_map()
384378
.partition_point(|segment| segment.offset < initial_offset);
385379

386-
let mut initial_read_segments = self.initial_read_segments.write();
380+
let mut initial_read_segments = HashMap::default();
387381

388382
for idx in first_idx..footer.segment_map().len() {
389383
let segment = &footer.segment_map()[idx];
@@ -410,7 +404,7 @@ impl VortexOpenOptions {
410404
initial_read_segments.insert(segment_id, buffer);
411405
}
412406

413-
Ok(())
407+
Ok(initial_read_segments)
414408
}
415409
}
416410

@@ -613,14 +607,14 @@ mod tests {
613607
);
614608
}
615609

616-
/// `populate_initial_segments` must bounds-check the segment map against the initial read rather
610+
/// `collect_initial_segments` must bounds-check the segment map against the initial read rather
617611
/// than slicing unchecked, so a segment larger than the read returns an error (see issue #8819).
618612
#[tokio::test]
619-
async fn populate_initial_segments_rejects_out_of_bounds_segment() -> VortexResult<()> {
613+
async fn collect_initial_segments_rejects_out_of_bounds_segment() -> VortexResult<()> {
620614
let session = test_session();
621615

622616
// A valid root layout is obtained by writing and parsing a small file.
623-
// `populate_initial_segments` only consults the segment map, so the layout is irrelevant.
617+
// `collect_initial_segments` only consults the segment map, so the layout is irrelevant.
624618
let mut buf = ByteBufferMut::empty();
625619
let array = Buffer::from((0i32..16).collect::<Vec<i32>>()).into_array();
626620
session
@@ -630,7 +624,8 @@ mod tests {
630624
let footer = session
631625
.open_options()
632626
.read_footer(&ByteBuffer::from(buf))
633-
.await?;
627+
.await?
628+
.footer;
634629

635630
// Build a footer whose sole segment is far larger than the initial read below.
636631
let bad_segments: Arc<[SegmentSpec]> = Arc::from([SegmentSpec {
@@ -646,12 +641,9 @@ mod tests {
646641
);
647642

648643
let initial_read = ByteBuffer::zeroed(16);
649-
let Err(err) =
650-
session
651-
.open_options()
652-
.populate_initial_segments(0, &initial_read, &bad_footer)
644+
let Err(err) = VortexOpenOptions::collect_initial_segments(0, &initial_read, &bad_footer)
653645
else {
654-
vortex_bail!("populating an out-of-bounds segment must return an error");
646+
vortex_bail!("collecting an out-of-bounds segment must return an error");
655647
};
656648
assert!(
657649
err.to_string().contains("out of bounds"),

vortex-file/src/segments/cache.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use std::sync::Arc;
55

66
use async_trait::async_trait;
7-
use parking_lot::RwLock;
87
use vortex_buffer::ByteBuffer;
98
use vortex_error::VortexResult;
109
use vortex_layout::segments::SegmentCache;
@@ -14,15 +13,15 @@ use vortex_utils::aliases::hash_map::HashMap;
1413
/// Segment cache containing the initial read segments.
1514
pub struct InitialReadSegmentCache {
1615
/// Segments that were already covered by the footer initial read.
17-
pub initial: RwLock<HashMap<SegmentId, ByteBuffer>>,
16+
pub initial: HashMap<SegmentId, ByteBuffer>,
1817
/// Delegate cache used for all misses and stores.
1918
pub fallback: Arc<dyn SegmentCache>,
2019
}
2120

2221
#[async_trait]
2322
impl SegmentCache for InitialReadSegmentCache {
2423
async fn get(&self, id: SegmentId) -> VortexResult<Option<ByteBuffer>> {
25-
if let Some(buffer) = self.initial.read().get(&id) {
24+
if let Some(buffer) = self.initial.get(&id) {
2625
return Ok(Some(buffer.clone()));
2726
}
2827
self.fallback.get(id).await

0 commit comments

Comments
 (0)