mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-13 17:46:39 +00:00

* [NOD-872] Defer unlocks in write.go. * [NOD-872] Defer unlocks in rollback.go. * [NOD-872] Defer unlocks in read.go. * [NOD-872] Fix duplicate RUnlock. * [NOD-872] Remove a redundant empty line. * [NOD-872] Extract closeCurrentWriteCursorFile to a separate method.
154 lines
5.4 KiB
Go
154 lines
5.4 KiB
Go
package ff
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/database"
|
|
"github.com/pkg/errors"
|
|
"hash/crc32"
|
|
"os"
|
|
)
|
|
|
|
// read reads the specified flat file record and returns the data. It ensures
|
|
// the integrity of the data by comparing the calculated checksum against the
|
|
// one stored in the flat file. This function also automatically handles all
|
|
// file management such as opening and closing files as necessary to stay
|
|
// within the maximum allowed open files limit. It returns ErrNotFound if the
|
|
// location does not exist.
|
|
//
|
|
// Format: <data length><data><checksum>
|
|
func (s *flatFileStore) read(location *flatFileLocation) ([]byte, error) {
|
|
if s.isClosed {
|
|
return nil, errors.Errorf("cannot read from a closed store %s",
|
|
s.storeName)
|
|
}
|
|
|
|
// Return not-found if the location is greater than or equal to
|
|
// the current write cursor.
|
|
if s.writeCursor.currentFileNumber < location.fileNumber ||
|
|
(s.writeCursor.currentFileNumber == location.fileNumber && s.writeCursor.currentOffset <= location.fileOffset) {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
|
|
// Get the referenced flat file.
|
|
flatFile, err := s.flatFile(location.fileNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
flatFile.RLock()
|
|
defer flatFile.RUnlock()
|
|
|
|
data := make([]byte, location.dataLength)
|
|
n, err := flatFile.file.ReadAt(data, int64(location.fileOffset))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to read data in store '%s' "+
|
|
"from file %d, offset %d", s.storeName, location.fileNumber,
|
|
location.fileOffset)
|
|
}
|
|
|
|
// Calculate the checksum of the read data and ensure it matches the
|
|
// serialized checksum.
|
|
serializedChecksum := crc32ByteOrder.Uint32(data[n-crc32ChecksumLength:])
|
|
calculatedChecksum := crc32.Checksum(data[:n-crc32ChecksumLength], castagnoli)
|
|
if serializedChecksum != calculatedChecksum {
|
|
return nil, errors.Errorf("data in store '%s' does not match "+
|
|
"checksum - got %x, want %x", s.storeName, calculatedChecksum,
|
|
serializedChecksum)
|
|
}
|
|
|
|
// The data excludes the length of the data and the checksum.
|
|
return data[dataLengthLength : n-crc32ChecksumLength], nil
|
|
}
|
|
|
|
// flatFile attempts to return an existing file handle for the passed flat file
|
|
// number if it is already open as well as marking it as most recently used. It
|
|
// will also open the file when it's not already open subject to the rules
|
|
// described in openFile. Also handles closing files as needed to avoid going
|
|
// over the max allowed open files.
|
|
func (s *flatFileStore) flatFile(fileNumber uint32) (*lockableFile, error) {
|
|
// When the requested flat file is open for writes, return it.
|
|
s.writeCursor.RLock()
|
|
defer s.writeCursor.RUnlock()
|
|
if fileNumber == s.writeCursor.currentFileNumber && s.writeCursor.currentFile.file != nil {
|
|
openFile := s.writeCursor.currentFile
|
|
return openFile, nil
|
|
}
|
|
|
|
// Try to return an open file under the overall files read lock.
|
|
s.openFilesMutex.RLock()
|
|
defer s.openFilesMutex.RUnlock()
|
|
if openFile, ok := s.openFiles[fileNumber]; ok {
|
|
s.lruMutex.Lock()
|
|
defer s.lruMutex.Unlock()
|
|
|
|
s.openFilesLRU.MoveToFront(s.fileNumberToLRUElement[fileNumber])
|
|
|
|
return openFile, nil
|
|
}
|
|
|
|
// Since the file isn't open already, need to check the open files map
|
|
// again under write lock in case multiple readers got here and a
|
|
// separate one is already opening the file.
|
|
if openFlatFile, ok := s.openFiles[fileNumber]; ok {
|
|
return openFlatFile, nil
|
|
}
|
|
|
|
// The file isn't open, so open it while potentially closing the least
|
|
// recently used one as needed.
|
|
openFile, err := s.openFile(fileNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return openFile, nil
|
|
}
|
|
|
|
// openFile returns a read-only file handle for the passed flat file number.
|
|
// The function also keeps track of the open files, performs least recently
|
|
// used tracking, and limits the number of open files to maxOpenFiles by closing
|
|
// the least recently used file as needed.
|
|
//
|
|
// This function MUST be called with the open files mutex (s.openFilesMutex)
|
|
// locked for WRITES.
|
|
func (s *flatFileStore) openFile(fileNumber uint32) (*lockableFile, error) {
|
|
// Open the appropriate file as read-only.
|
|
filePath := flatFilePath(s.basePath, s.storeName, fileNumber)
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
flatFile := &lockableFile{file: file}
|
|
|
|
// Close the least recently used file if the file exceeds the max
|
|
// allowed open files. This is not done until after the file open in
|
|
// case the file fails to open, there is no need to close any files.
|
|
//
|
|
// A write lock is required on the LRU list here to protect against
|
|
// modifications happening as already open files are read from and
|
|
// shuffled to the front of the list.
|
|
//
|
|
// Also, add the file that was just opened to the front of the least
|
|
// recently used list to indicate it is the most recently used file and
|
|
// therefore should be closed last.
|
|
s.lruMutex.Lock()
|
|
defer s.lruMutex.Unlock()
|
|
lruList := s.openFilesLRU
|
|
if lruList.Len() >= maxOpenFiles {
|
|
lruFileNumber := lruList.Remove(lruList.Back()).(uint32)
|
|
oldFile := s.openFiles[lruFileNumber]
|
|
|
|
// Close the old file under the write lock for the file in case
|
|
// any readers are currently reading from it so it's not closed
|
|
// out from under them.
|
|
oldFile.Lock()
|
|
defer oldFile.Unlock()
|
|
_ = oldFile.file.Close()
|
|
|
|
delete(s.openFiles, lruFileNumber)
|
|
delete(s.fileNumberToLRUElement, lruFileNumber)
|
|
}
|
|
s.fileNumberToLRUElement[fileNumber] = lruList.PushFront(fileNumber)
|
|
|
|
// Store a reference to it in the open files map.
|
|
s.openFiles[fileNumber] = flatFile
|
|
|
|
return flatFile, nil
|
|
}
|