github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 }