mirror of
https://github.com/benbjohnson/litestream.git
synced 2026-01-24 20:56:48 +00:00
fix(vfs): fail fast when write buffer initialization fails (#974)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
15
vfs.go
15
vfs.go
@@ -591,7 +591,7 @@ func (f *VFSFile) Open() error {
|
||||
|
||||
// Initialize write buffer file for durability (discards any existing buffer)
|
||||
if err := f.initWriteBuffer(); err != nil {
|
||||
f.logger.Warn("failed to initialize write buffer", "error", err)
|
||||
return fmt.Errorf("initialize write buffer: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,11 +1256,8 @@ func (f *VFSFile) createLTXFromDirty() io.Reader {
|
||||
|
||||
// initWriteBuffer initializes the write buffer file for durability.
|
||||
// Any existing buffer content is discarded since unsync'd changes are lost on restart.
|
||||
// This function is only called when writeEnabled is true, which guarantees bufferPath is set.
|
||||
func (f *VFSFile) initWriteBuffer() error {
|
||||
if f.bufferPath == "" {
|
||||
return nil // No buffer configured
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(f.bufferPath), 0755); err != nil {
|
||||
return fmt.Errorf("create buffer directory: %w", err)
|
||||
@@ -1282,10 +1279,6 @@ func (f *VFSFile) initWriteBuffer() error {
|
||||
// Otherwise, it appends to the end of the file.
|
||||
// Must be called with f.mu held.
|
||||
func (f *VFSFile) writeToBuffer(pgno uint32, data []byte) error {
|
||||
if f.bufferFile == nil {
|
||||
return nil // No buffer configured
|
||||
}
|
||||
|
||||
var writeOffset int64
|
||||
if existingOff, ok := f.dirty[pgno]; ok {
|
||||
// Page already exists - overwrite at same offset
|
||||
@@ -1309,10 +1302,6 @@ func (f *VFSFile) writeToBuffer(pgno uint32, data []byte) error {
|
||||
|
||||
// clearWriteBuffer clears and resets the write buffer after successful sync.
|
||||
func (f *VFSFile) clearWriteBuffer() error {
|
||||
if f.bufferFile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Truncate file to zero
|
||||
if err := f.bufferFile.Truncate(0); err != nil {
|
||||
return fmt.Errorf("truncate buffer: %w", err)
|
||||
|
||||
@@ -229,11 +229,15 @@ func TestVFSFile_WriteEnabled(t *testing.T) {
|
||||
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
|
||||
|
||||
// Create VFSFile directly with write enabled
|
||||
tmpDir := t.TempDir()
|
||||
bufferPath := tmpDir + "/write-buffer"
|
||||
|
||||
logger := slog.Default()
|
||||
f := NewVFSFile(client, "test.db", logger)
|
||||
f.writeEnabled = true
|
||||
f.dirty = make(map[uint32]int64)
|
||||
f.syncInterval = 0
|
||||
f.bufferPath = bufferPath
|
||||
|
||||
if err := f.Open(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -639,6 +643,54 @@ func TestVFSFile_WriteBufferClearAfterSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVFSFile_OpenFailsWithInvalidBufferPath(t *testing.T) {
|
||||
client := newWriteTestReplicaClient()
|
||||
|
||||
pageSize := uint32(4096)
|
||||
initialPage := make([]byte, pageSize)
|
||||
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
|
||||
|
||||
logger := slog.Default()
|
||||
f := NewVFSFile(client, "test.db", logger)
|
||||
f.writeEnabled = true
|
||||
f.dirty = make(map[uint32]int64)
|
||||
f.syncInterval = 0
|
||||
f.bufferPath = "/nonexistent/path/that/cannot/be/created/buffer"
|
||||
|
||||
err := f.Open()
|
||||
if err == nil {
|
||||
f.Close()
|
||||
t.Fatal("expected Open to fail with invalid buffer path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVFSFile_BufferFileAlwaysCreatedWhenWriteEnabled(t *testing.T) {
|
||||
client := newWriteTestReplicaClient()
|
||||
|
||||
pageSize := uint32(4096)
|
||||
initialPage := make([]byte, pageSize)
|
||||
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
bufferPath := tmpDir + "/write-buffer"
|
||||
|
||||
logger := slog.Default()
|
||||
f := NewVFSFile(client, "test.db", logger)
|
||||
f.writeEnabled = true
|
||||
f.dirty = make(map[uint32]int64)
|
||||
f.syncInterval = 0
|
||||
f.bufferPath = bufferPath
|
||||
|
||||
if err := f.Open(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if f.bufferFile == nil {
|
||||
t.Fatal("bufferFile should never be nil when writeEnabled is true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVFSFile_OpenNewDatabase(t *testing.T) {
|
||||
// Test opening a VFSFile with write mode enabled when no LTX files exist (new database)
|
||||
client := newWriteTestReplicaClient()
|
||||
|
||||
Reference in New Issue
Block a user