lib/fs: replace WriteFileAndSync with MustWriteAndSync

When WriteFileAndSync fails, then the caller eventually logs the error message
and exits. The error message returned by WriteFileAndSync already contains the path
to the file, which couldn't be created. This information alongside the call stack
is enough for debugging the issue. So just use log.Panicf("FATAL: ...") inside MustWriteAndSync().
This simplifies error handling at caller side a bit.
This commit is contained in:
Aliaksandr Valialkin 2023-04-13 21:33:15 -07:00
parent eb7df27e20
commit 1cda542c48
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
7 changed files with 31 additions and 54 deletions

View file

@ -25,7 +25,7 @@ func MustSyncPath(path string) {
mustSyncPath(path) mustSyncPath(path)
} }
// WriteFileAndSync writes data to the file at path and then calls fsync on the created file. // MustWriteFileAndSync writes data to the file at path and then calls fsync on the created file.
// //
// The fsync guarantees that the written data survives hardware reset after successful call. // The fsync guarantees that the written data survives hardware reset after successful call.
// //
@ -33,20 +33,19 @@ func MustSyncPath(path string) {
// in the middle of the write. // in the middle of the write.
// Use WriteFileAtomically if the file at the path must be either written in full // Use WriteFileAtomically if the file at the path must be either written in full
// or not written at all on app crash in the middle of the write. // or not written at all on app crash in the middle of the write.
func WriteFileAndSync(path string, data []byte) error { func MustWriteFileAndSync(path string, data []byte) {
f, err := filestream.Create(path, false) f, err := filestream.Create(path, false)
if err != nil { if err != nil {
return err logger.Panicf("FATAL: cannot create file: %s", err)
} }
if _, err := f.Write(data); err != nil { if _, err := f.Write(data); err != nil {
f.MustClose() f.MustClose()
// Do not call MustRemoveAll(path), so the user could inspect // Do not call MustRemoveAll(path), so the user could inspect
// the file contents during investigation of the issue. // the file contents during investigation of the issue.
return fmt.Errorf("cannot write %d bytes to %q: %w", len(data), path, err) logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(data), path, err)
} }
// Sync and close the file. // Sync and close the file.
f.MustClose() f.MustClose()
return nil
} }
// WriteFileAtomically atomically writes data to the given file path. // WriteFileAtomically atomically writes data to the given file path.
@ -70,9 +69,7 @@ func WriteFileAtomically(path string, data []byte, canOverwrite bool) error {
// Write data to a temporary file. // Write data to a temporary file.
n := atomic.AddUint64(&tmpFileNum, 1) n := atomic.AddUint64(&tmpFileNum, 1)
tmpPath := fmt.Sprintf("%s.tmp.%d", path, n) tmpPath := fmt.Sprintf("%s.tmp.%d", path, n)
if err := WriteFileAndSync(tmpPath, data); err != nil { MustWriteFileAndSync(tmpPath, data)
return fmt.Errorf("cannot write data to temporary file: %w", err)
}
// Atomically move the temporary file from tmpPath to path. // Atomically move the temporary file from tmpPath to path.
if err := os.Rename(tmpPath, path); err != nil { if err := os.Rename(tmpPath, path); err != nil {

View file

@ -38,24 +38,19 @@ func (mp *inmemoryPart) StoreToDisk(path string) error {
return fmt.Errorf("cannot create directory %q: %w", path, err) return fmt.Errorf("cannot create directory %q: %w", path, err)
} }
metaindexPath := filepath.Join(path, metaindexFilename) metaindexPath := filepath.Join(path, metaindexFilename)
if err := fs.WriteFileAndSync(metaindexPath, mp.metaindexData.B); err != nil { fs.MustWriteFileAndSync(metaindexPath, mp.metaindexData.B)
return fmt.Errorf("cannot store metaindex: %w", err)
}
indexPath := filepath.Join(path, indexFilename) indexPath := filepath.Join(path, indexFilename)
if err := fs.WriteFileAndSync(indexPath, mp.indexData.B); err != nil { fs.MustWriteFileAndSync(indexPath, mp.indexData.B)
return fmt.Errorf("cannot store index: %w", err)
}
itemsPath := filepath.Join(path, itemsFilename) itemsPath := filepath.Join(path, itemsFilename)
if err := fs.WriteFileAndSync(itemsPath, mp.itemsData.B); err != nil { fs.MustWriteFileAndSync(itemsPath, mp.itemsData.B)
return fmt.Errorf("cannot store items: %w", err)
}
lensPath := filepath.Join(path, lensFilename) lensPath := filepath.Join(path, lensFilename)
if err := fs.WriteFileAndSync(lensPath, mp.lensData.B); err != nil { fs.MustWriteFileAndSync(lensPath, mp.lensData.B)
return fmt.Errorf("cannot store lens: %w", err)
} mp.ph.MustWriteMetadata(path)
if err := mp.ph.WriteMetadata(path); err != nil {
return fmt.Errorf("cannot store metadata: %w", err)
}
fs.MustSyncPath(path) fs.MustSyncPath(path)
// Do not sync parent directory - it must be synced by the caller. // Do not sync parent directory - it must be synced by the caller.
return nil return nil

View file

@ -113,7 +113,7 @@ func (ph *partHeader) ReadMetadata(partPath string) error {
return nil return nil
} }
func (ph *partHeader) WriteMetadata(partPath string) error { func (ph *partHeader) MustWriteMetadata(partPath string) {
phj := &partHeaderJSON{ phj := &partHeaderJSON{
ItemsCount: ph.itemsCount, ItemsCount: ph.itemsCount,
BlocksCount: ph.blocksCount, BlocksCount: ph.blocksCount,
@ -128,8 +128,5 @@ func (ph *partHeader) WriteMetadata(partPath string) error {
// There is no need in calling fs.WriteFileAtomically() here, // There is no need in calling fs.WriteFileAtomically() here,
// since the file is created only once during part creatinng // since the file is created only once during part creatinng
// and the part directory is synced aftewards. // and the part directory is synced aftewards.
if err := fs.WriteFileAndSync(metadataPath, metadata); err != nil { fs.MustWriteFileAndSync(metadataPath, metadata)
return fmt.Errorf("cannot create %q: %w", metadataPath, err)
}
return nil
} }

View file

@ -1245,9 +1245,7 @@ func (tb *Table) mergePartsInternal(dstPartPath string, bsw *blockStreamWriter,
return nil, fmt.Errorf("cannot merge %d parts to %s: %w", len(bsrs), dstPartPath, err) return nil, fmt.Errorf("cannot merge %d parts to %s: %w", len(bsrs), dstPartPath, err)
} }
if dstPartPath != "" { if dstPartPath != "" {
if err := ph.WriteMetadata(dstPartPath); err != nil { ph.MustWriteMetadata(dstPartPath)
logger.Panicf("FATAL: cannot write metadata to %s: %s", dstPartPath, err)
}
} }
return &ph, nil return &ph, nil
} }

View file

@ -41,24 +41,19 @@ func (mp *inmemoryPart) StoreToDisk(path string) error {
return fmt.Errorf("cannot create directory %q: %w", path, err) return fmt.Errorf("cannot create directory %q: %w", path, err)
} }
timestampsPath := filepath.Join(path, timestampsFilename) timestampsPath := filepath.Join(path, timestampsFilename)
if err := fs.WriteFileAndSync(timestampsPath, mp.timestampsData.B); err != nil { fs.MustWriteFileAndSync(timestampsPath, mp.timestampsData.B)
return fmt.Errorf("cannot store timestamps: %w", err)
}
valuesPath := filepath.Join(path, valuesFilename) valuesPath := filepath.Join(path, valuesFilename)
if err := fs.WriteFileAndSync(valuesPath, mp.valuesData.B); err != nil { fs.MustWriteFileAndSync(valuesPath, mp.valuesData.B)
return fmt.Errorf("cannot store values: %w", err)
}
indexPath := filepath.Join(path, indexFilename) indexPath := filepath.Join(path, indexFilename)
if err := fs.WriteFileAndSync(indexPath, mp.indexData.B); err != nil { fs.MustWriteFileAndSync(indexPath, mp.indexData.B)
return fmt.Errorf("cannot store index: %w", err)
}
metaindexPath := filepath.Join(path, metaindexFilename) metaindexPath := filepath.Join(path, metaindexFilename)
if err := fs.WriteFileAndSync(metaindexPath, mp.metaindexData.B); err != nil { fs.MustWriteFileAndSync(metaindexPath, mp.metaindexData.B)
return fmt.Errorf("cannot store metaindex: %w", err)
} mp.ph.MustWriteMetadata(path)
if err := mp.ph.WriteMetadata(path); err != nil {
return fmt.Errorf("cannot store metadata: %w", err)
}
fs.MustSyncPath(path) fs.MustSyncPath(path)
// Do not sync parent directory - it must be synced by the caller. // Do not sync parent directory - it must be synced by the caller.
return nil return nil

View file

@ -165,7 +165,7 @@ func (ph *partHeader) ReadMetadata(partPath string) error {
return nil return nil
} }
func (ph *partHeader) WriteMetadata(partPath string) error { func (ph *partHeader) MustWriteMetadata(partPath string) {
metadata, err := json.Marshal(ph) metadata, err := json.Marshal(ph)
if err != nil { if err != nil {
logger.Panicf("BUG: cannot marshal partHeader metadata: %s", err) logger.Panicf("BUG: cannot marshal partHeader metadata: %s", err)
@ -174,8 +174,5 @@ func (ph *partHeader) WriteMetadata(partPath string) error {
// There is no need in calling fs.WriteFileAtomically() here, // There is no need in calling fs.WriteFileAtomically() here,
// since the file is created only once during part creatinng // since the file is created only once during part creatinng
// and the part directory is synced aftewards. // and the part directory is synced aftewards.
if err := fs.WriteFileAndSync(metadataPath, metadata); err != nil { fs.MustWriteFileAndSync(metadataPath, metadata)
return fmt.Errorf("cannot create %q: %w", metadataPath, err)
}
return nil
} }

View file

@ -1465,9 +1465,7 @@ func (pt *partition) mergePartsInternal(dstPartPath string, bsw *blockStreamWrit
} }
if dstPartPath != "" { if dstPartPath != "" {
ph.MinDedupInterval = GetDedupInterval() ph.MinDedupInterval = GetDedupInterval()
if err := ph.WriteMetadata(dstPartPath); err != nil { ph.MustWriteMetadata(dstPartPath)
logger.Panicf("FATAL: cannot store metadata to %s: %s", dstPartPath, err)
}
} }
return &ph, nil return &ph, nil
} }