@@ -505,11 +505,12 @@ internal override void Initialize()
505
505
if ( RandomAccess . Read ( Handle , header . Span , fileOffset : 0L ) < HeaderSize )
506
506
{
507
507
header . Span . Clear ( ) ;
508
+ writer . FilePosition = HeaderSize ;
508
509
}
509
510
else if ( IsSealed )
510
511
{
511
512
// partition is completed, read table
512
- fileOffset = RandomAccess . GetLength ( Handle ) ;
513
+ writer . FilePosition = fileOffset = RandomAccess . GetLength ( Handle ) ;
513
514
514
515
if ( fileOffset < footer . Length + HeaderSize )
515
516
throw new IntegrityException ( ExceptionMessages . InvalidPartitionFormat ) ;
@@ -533,7 +534,7 @@ internal override void Initialize()
533
534
fileOffset = HeaderSize ;
534
535
}
535
536
536
- for ( Span < byte > metadataBuffer = stackalloc byte [ LogEntryMetadata . Size ] , metadataTable = footer . Span ; ; footerOffset += LogEntryMetadata . Size )
537
+ for ( Span < byte > metadataBuffer = stackalloc byte [ LogEntryMetadata . Size ] , metadataTable = footer . Span ; footerOffset < footer . Length ; footerOffset += LogEntryMetadata . Size )
537
538
{
538
539
var count = RandomAccess . Read ( Handle , metadataBuffer , fileOffset ) ;
539
540
if ( count < LogEntryMetadata . Size )
@@ -543,6 +544,7 @@ internal override void Initialize()
543
544
if ( fileOffset <= 0L )
544
545
break ;
545
546
547
+ writer . FilePosition = fileOffset ;
546
548
metadataBuffer . CopyTo ( metadataTable . Slice ( footerOffset , LogEntryMetadata . Size ) ) ;
547
549
}
548
550
}
@@ -562,6 +564,7 @@ private long GetWriteAddress(int index)
562
564
protected override async ValueTask PersistAsync < TEntry > ( TEntry entry , int index , CancellationToken token )
563
565
{
564
566
var writeAddress = GetWriteAddress ( index ) ;
567
+ await UnsealIfNeededAsync ( writeAddress , token ) . ConfigureAwait ( false ) ;
565
568
566
569
LogEntryMetadata metadata ;
567
570
Memory < byte > metadataBuffer ;
@@ -601,6 +604,8 @@ protected override async ValueTask WriteThroughAsync(CachedLogEntry entry, int i
601
604
Debug . Assert ( writer . HasBufferedData is false ) ;
602
605
603
606
var writeAddress = GetWriteAddress ( index ) ;
607
+ await UnsealIfNeededAsync ( writeAddress , token ) . ConfigureAwait ( false ) ;
608
+
604
609
var startPos = writeAddress + LogEntryMetadata . Size ;
605
610
var metadata = LogEntryMetadata . Create ( entry , startPos , entry . Length ) ;
606
611
var metadataBuffer = GetMetadataBuffer ( index ) ;
@@ -621,12 +626,59 @@ protected override void OnCached(in CachedLogEntry cachedEntry, int index)
621
626
metadata . Format ( GetMetadataBuffer ( index ) . Span ) ;
622
627
}
623
628
629
+ private ValueTask UnsealIfNeededAsync ( long truncatePosition , CancellationToken token )
630
+ {
631
+ ValueTask task ;
632
+ if ( IsSealed )
633
+ {
634
+ task = UnsealAsync ( truncatePosition , token ) ;
635
+ }
636
+ else if ( token . IsCancellationRequested )
637
+ {
638
+ task = ValueTask . FromCanceled ( token ) ;
639
+ }
640
+ else if ( truncatePosition < writer . FilePosition )
641
+ {
642
+ task = new ( ) ;
643
+ try
644
+ {
645
+ // The caller is trying to rewrite the log entry.
646
+ // For a correctness of Initialize() method for unsealed partitions, we
647
+ // need to adjust file size. This is expensive syscall which can lead to file fragmentation.
648
+ // However, this is acceptable because rare.
649
+ RandomAccess . SetLength ( Handle , truncatePosition ) ;
650
+ }
651
+ catch ( Exception e )
652
+ {
653
+ task = ValueTask . FromException ( e ) ;
654
+ }
655
+ }
656
+ else
657
+ {
658
+ task = new ( ) ;
659
+ }
660
+
661
+ return task ;
662
+ }
663
+
664
+ private async ValueTask UnsealAsync ( long truncatePosition , CancellationToken token )
665
+ {
666
+ // This is expensive operation in terms of I/O. However, it is needed only when
667
+ // the consumer decided to rewrite the existing log entry, which is rare.
668
+ IsSealed = false ;
669
+ await WriteHeaderAsync ( token ) . ConfigureAwait ( false ) ;
670
+ RandomAccess . FlushToDisk ( Handle ) ;
671
+
672
+ // destroy all entries in the tail of partition
673
+ RandomAccess . SetLength ( Handle , truncatePosition ) ;
674
+ }
675
+
624
676
public override ValueTask FlushAsync ( CancellationToken token = default )
625
677
{
626
- return runningIndex == LastIndex
678
+ return IsSealed
679
+ ? ValueTask . CompletedTask
680
+ : runningIndex == LastIndex
627
681
? FlushAndSealAsync ( token )
628
- : IsSealed
629
- ? FlushAndUnsealAsync ( token )
630
682
: base . FlushAsync ( token ) ;
631
683
}
632
684
@@ -651,41 +703,7 @@ private async ValueTask FlushAndSealAsync(CancellationToken token)
651
703
IsSealed = true ;
652
704
await WriteHeaderAsync ( token ) . ConfigureAwait ( false ) ;
653
705
654
- writer . FlushToDisk ( ) ;
655
- }
656
-
657
- private async ValueTask FlushAndUnsealAsync ( CancellationToken token )
658
- {
659
- await FlushAndEraseNextEntryAsync ( token ) . ConfigureAwait ( false ) ;
660
-
661
- IsSealed = false ;
662
- await WriteHeaderAsync ( token ) . ConfigureAwait ( false ) ;
663
-
664
- writer . FlushToDisk ( ) ;
665
- }
666
-
667
- private ValueTask FlushAndEraseNextEntryAsync ( CancellationToken token )
668
- {
669
- ValueTask task ;
670
-
671
- // write the rest of the entry,
672
- // then cleanup next entry header to indicate that the current entry is the last entry
673
- if ( ! writer . HasBufferedData )
674
- {
675
- task = RandomAccess . WriteAsync ( Handle , EmptyMetadata , writer . FilePosition , token ) ;
676
- }
677
- else if ( writer . Buffer is { Length : >= LogEntryMetadata . Size } emptyMetadataStub )
678
- {
679
- emptyMetadataStub . Span . Slice ( 0 , LogEntryMetadata . Size ) . Clear ( ) ;
680
- writer . Produce ( LogEntryMetadata . Size ) ;
681
- task = writer . WriteAsync ( token ) ;
682
- }
683
- else
684
- {
685
- task = writer . WriteAsync ( EmptyMetadata , token ) ;
686
- }
687
-
688
- return task ;
706
+ RandomAccess . FlushToDisk ( Handle ) ;
689
707
}
690
708
691
709
private ValueTask WriteHeaderAsync ( CancellationToken token )
0 commit comments