github.com/koko1123/flow-go-1@v0.29.6/ledger/complete/wal/syncrename.go (about)

     1  package wal
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/rs/zerolog"
    10  )
    11  
    12  type WriterSeekerCloser interface {
    13  	io.Writer
    14  	io.Seeker
    15  	io.Closer
    16  }
    17  
    18  // SyncOnCloseRenameFile is a composite of buffered writer over a given file
    19  // which flushes/sync on closing and renames to `targetName` as a last step.
    20  // Typical usecase is to write data to a temporary file and only rename it
    21  // to target one as the last step. This help avoid situation when writing is
    22  // interrupted  and unusable file but with target name exists.
    23  type SyncOnCloseRenameFile struct {
    24  	logger     *zerolog.Logger
    25  	file       *os.File
    26  	targetName string
    27  	savedError error // savedError is the first error returned from Write.  Close() renames temp file to target file only if savedError is nil.
    28  	*bufio.Writer
    29  }
    30  
    31  func (s *SyncOnCloseRenameFile) Sync() error {
    32  	err := s.Flush()
    33  	if err != nil {
    34  		return fmt.Errorf("cannot flush buffer: %w", err)
    35  	}
    36  	return s.file.Sync()
    37  
    38  }
    39  
    40  func (s *SyncOnCloseRenameFile) Close() error {
    41  	if s.savedError != nil {
    42  		// If there is any error saved from previous op, close temp file without renaming to target file.
    43  		return s.closeOnError()
    44  	}
    45  
    46  	err := s.Flush()
    47  	if err != nil {
    48  		return fmt.Errorf("cannot flush buffer: %w", err)
    49  	}
    50  
    51  	err = s.file.Sync()
    52  	if err != nil {
    53  		return fmt.Errorf("cannot sync file %s: %w", s.file.Name(), err)
    54  	}
    55  
    56  	// s.file.Sync() was already called, so we pass fsync=false
    57  	err = evictFileFromLinuxPageCache(s.file, false, s.logger)
    58  	if err != nil {
    59  		s.logger.Warn().Msgf("failed to evict file %s from Linux page cache: %s", s.targetName, err)
    60  		// No need to return this error because we're only "advising" Linux to evict a file from cache.
    61  	}
    62  
    63  	err = s.file.Close()
    64  	if err != nil {
    65  		return fmt.Errorf("error while closing file %s: %w", s.file.Name(), err)
    66  	}
    67  
    68  	err = os.Rename(s.file.Name(), s.targetName)
    69  	if err != nil {
    70  		return fmt.Errorf("error while renaming from %s to %s: %w", s.file.Name(), s.targetName, err)
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  func (s *SyncOnCloseRenameFile) Write(b []byte) (int, error) {
    77  	n, err := s.Writer.Write(b)
    78  	if err != nil && s.savedError == nil {
    79  		s.savedError = err
    80  	}
    81  	return n, err
    82  }
    83  
    84  // closeOnError closes and deletes temp file.
    85  func (s *SyncOnCloseRenameFile) closeOnError() error {
    86  	fileName := s.file.Name()
    87  
    88  	// Close temp file
    89  	err := s.file.Close()
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	// Remove temp file because it is incomplete/invalid.
    95  	return os.Remove(fileName)
    96  }