@@ -15,7 +15,7 @@ use crate::{
1515 error:: { self , source_chain} ,
1616 index:: IndexError ,
1717 payload:: Decoder ,
18- repo:: { self , Repo , TxOffsetIndex } ,
18+ repo:: { self , Repo , SegmentLen as _ , TxOffsetIndex } ,
1919 segment:: { self , FileLike , Transaction , Writer } ,
2020 Commit , Encode , Options , DEFAULT_LOG_FORMAT_VERSION ,
2121} ;
@@ -355,7 +355,142 @@ impl<R: Repo, T> Drop for Generic<R, T> {
355355 }
356356}
357357
358- /// Extract the most recently written [`segment::Metadata`] from the commitlog
358+ /// The most recent non empty segment in repo `R`.
359+ ///
360+ /// Created by [open_newest_non_empty_segment].
361+ struct MostRecentNonEmptySegment < R > {
362+ /// Number of empty segments that were ignored.
363+ empty_segments : usize ,
364+ /// Offset of the non-empty segment.
365+ segment_offset : u64 ,
366+ /// [Repo::SegmentReader] for the non-empty segment.
367+ segment_reader : R ,
368+ }
369+
370+ /// Open the most recent segment in `repo` that is larger than
371+ /// [segment::Header::LEN].
372+ ///
373+ /// Note that there should be at most one empty segment in the log. We may,
374+ /// however, want to be lenient on this read-only path, so the number of
375+ /// empty segments is tracked in the returned type rather than returning an
376+ /// error.
377+ fn open_newest_non_empty_segment < R : Repo > ( repo : R ) -> io:: Result < Option < MostRecentNonEmptySegment < R :: SegmentReader > > > {
378+ let mut segments = repo. existing_offsets ( ) ?;
379+
380+ let mut empty_segments = 0 ;
381+ let mut segment_offset;
382+ let mut segment_reader;
383+ loop {
384+ let Some ( last) = segments. pop ( ) else {
385+ return Ok ( None ) ;
386+ } ;
387+ segment_offset = last;
388+ segment_reader = repo. open_segment_reader ( segment_offset) ?;
389+ if segment_reader. segment_len ( ) ? > segment:: Header :: LEN as u64 {
390+ break ;
391+ } else {
392+ empty_segments += 1 ;
393+ }
394+ }
395+
396+ Ok ( Some ( MostRecentNonEmptySegment {
397+ empty_segments,
398+ segment_offset,
399+ segment_reader,
400+ } ) )
401+ }
402+
403+ /// The most recently written [segment::Metadata] for a given [Repo].
404+ ///
405+ /// The type preserves the error information in case the most recent segment
406+ /// contains corrupted data at the end (typically due to a torn write).
407+ ///
408+ /// Created by [committed_meta].
409+ pub enum CommittedMeta {
410+ /// The most recent segment could not be traversed successfully until the
411+ /// end, i.e. there is trailing garbage in the segment.
412+ ///
413+ /// This variant is also returned in case [open_newest_non_empty_segment]
414+ /// finds more than a single empty segment at the end of the log.
415+ Prefix {
416+ /// The metadata of the prefix that could be traversed successfully.
417+ ///
418+ /// It is guaranteed that the metadata spans at least one commit.
419+ metadata : segment:: Metadata ,
420+ /// The error encountered.
421+ error : io:: Error ,
422+ } ,
423+ /// The most recent segment could be traversed successfully until the end.
424+ Complete {
425+ /// The segment metadata.
426+ ///
427+ /// It is guaranteed that the metadata spans at least one commit.
428+ metadata : segment:: Metadata ,
429+ } ,
430+ }
431+
432+ impl CommittedMeta {
433+ pub fn metadata ( & self ) -> & segment:: Metadata {
434+ let ( Self :: Prefix { metadata, .. } | Self :: Complete { metadata } ) = self ;
435+ metadata
436+ }
437+
438+ fn extract ( repo : impl Repo ) -> io:: Result < Option < Self > > {
439+ let Some ( MostRecentNonEmptySegment {
440+ empty_segments,
441+ segment_offset,
442+ mut segment_reader,
443+ } ) = open_newest_non_empty_segment ( & repo) ?
444+ else {
445+ return Ok ( None ) ;
446+ } ;
447+ let offset_index = repo. get_offset_index ( segment_offset) . ok ( ) ;
448+ match segment:: Metadata :: extract ( segment_offset, & mut segment_reader, offset_index. as_ref ( ) ) {
449+ // Segment is intact.
450+ Ok ( metadata) if empty_segments <= 1 => {
451+ assert ! (
452+ !metadata. tx_range. is_empty( ) ,
453+ "segment was promised to be non-empty but contains zero transactions"
454+ ) ;
455+ Ok ( Some ( CommittedMeta :: Complete { metadata } ) )
456+ }
457+ // Segment is good, but there are too many empty segments.
458+ Ok ( metadata) => Ok ( Some ( CommittedMeta :: Prefix {
459+ metadata,
460+ error : io:: Error :: new (
461+ io:: ErrorKind :: InvalidData ,
462+ format ! ( "repo {}: too many empty segments: {}" , repo, empty_segments) ,
463+ ) ,
464+ } ) ) ,
465+ // Segment is non-empty, but first commit is corrupt.
466+ Err ( error:: SegmentMetadata :: InvalidCommit { sofar, source } ) if sofar. tx_range . is_empty ( ) => {
467+ Err ( io:: Error :: new (
468+ io:: ErrorKind :: InvalidData ,
469+ format ! (
470+ "repo {}: first commit in the most recent segment is corrupt: {}" ,
471+ repo, source
472+ ) ,
473+ ) )
474+ }
475+ // Some prefix of the segment is good.
476+ Err ( error:: SegmentMetadata :: InvalidCommit { sofar, source } ) => Ok ( Some ( CommittedMeta :: Prefix {
477+ metadata : sofar,
478+ error : source,
479+ } ) ) ,
480+ // Something went wrong, including out-of-order errors and such.
481+ Err ( error:: SegmentMetadata :: Io ( e) ) => Err ( e) ,
482+ }
483+ }
484+ }
485+
486+ impl From < CommittedMeta > for segment:: Metadata {
487+ fn from ( meta : CommittedMeta ) -> Self {
488+ let ( CommittedMeta :: Prefix { metadata, .. } | CommittedMeta :: Complete { metadata } ) = meta;
489+ metadata
490+ }
491+ }
492+
493+ /// Extract the most recently written [CommittedMeta] from the commitlog
359494/// in `repo`.
360495///
361496/// Returns `None` if the commitlog is empty.
@@ -373,18 +508,12 @@ impl<R: Repo, T> Drop for Generic<R, T> {
373508/// like so:
374509///
375510/// ```ignore
376- /// let max_offset = committed_meta(..)?.map(|meta| meta.tx_range.end);
511+ /// let max_offset = committed_meta(..)?.map(|meta| meta.metadata(). tx_range.end);
377512/// ```
378513///
379514/// Unlike `open`, no segment will be created in an empty `repo`.
380- pub fn committed_meta ( repo : impl Repo ) -> Result < Option < segment:: Metadata > , error:: SegmentMetadata > {
381- let Some ( last) = repo. existing_offsets ( ) ?. pop ( ) else {
382- return Ok ( None ) ;
383- } ;
384-
385- let mut storage = repo. open_segment_reader ( last) ?;
386- let offset_index = repo. get_offset_index ( last) . ok ( ) ;
387- segment:: Metadata :: extract ( last, & mut storage, offset_index. as_ref ( ) ) . map ( Some )
515+ pub fn committed_meta ( repo : impl Repo ) -> io:: Result < Option < CommittedMeta > > {
516+ CommittedMeta :: extract ( repo)
388517}
389518
390519pub fn commits_from < R : Repo > ( repo : R , max_log_format_version : u8 , offset : u64 ) -> io:: Result < Commits < R > > {
0 commit comments