44use std:: sync:: Arc ;
55
66use futures:: executor:: block_on;
7- use parking_lot:: RwLock ;
87use vortex_array:: dtype:: DType ;
98use vortex_array:: memory:: MemorySessionExt ;
109use vortex_array:: session:: ArraySessionExt ;
@@ -41,13 +40,19 @@ use crate::segments::RequestMetrics;
4140
4241const 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 ) ]
5156pub 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.
9278pub 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" ) ,
0 commit comments