Skip to content

Commit 7a38f8b

Browse files
corylanouclaude
andauthored
fix(vfs): fail fast when write buffer initialization fails (#974)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9be0eb1 commit 7a38f8b

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

vfs.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ func (f *VFSFile) Open() error {
591591

592592
// Initialize write buffer file for durability (discards any existing buffer)
593593
if err := f.initWriteBuffer(); err != nil {
594-
f.logger.Warn("failed to initialize write buffer", "error", err)
594+
return fmt.Errorf("initialize write buffer: %w", err)
595595
}
596596
}
597597

@@ -1256,11 +1256,8 @@ func (f *VFSFile) createLTXFromDirty() io.Reader {
12561256

12571257
// initWriteBuffer initializes the write buffer file for durability.
12581258
// Any existing buffer content is discarded since unsync'd changes are lost on restart.
1259+
// This function is only called when writeEnabled is true, which guarantees bufferPath is set.
12591260
func (f *VFSFile) initWriteBuffer() error {
1260-
if f.bufferPath == "" {
1261-
return nil // No buffer configured
1262-
}
1263-
12641261
// Ensure parent directory exists
12651262
if err := os.MkdirAll(filepath.Dir(f.bufferPath), 0755); err != nil {
12661263
return fmt.Errorf("create buffer directory: %w", err)
@@ -1282,10 +1279,6 @@ func (f *VFSFile) initWriteBuffer() error {
12821279
// Otherwise, it appends to the end of the file.
12831280
// Must be called with f.mu held.
12841281
func (f *VFSFile) writeToBuffer(pgno uint32, data []byte) error {
1285-
if f.bufferFile == nil {
1286-
return nil // No buffer configured
1287-
}
1288-
12891282
var writeOffset int64
12901283
if existingOff, ok := f.dirty[pgno]; ok {
12911284
// Page already exists - overwrite at same offset
@@ -1309,10 +1302,6 @@ func (f *VFSFile) writeToBuffer(pgno uint32, data []byte) error {
13091302

13101303
// clearWriteBuffer clears and resets the write buffer after successful sync.
13111304
func (f *VFSFile) clearWriteBuffer() error {
1312-
if f.bufferFile == nil {
1313-
return nil
1314-
}
1315-
13161305
// Truncate file to zero
13171306
if err := f.bufferFile.Truncate(0); err != nil {
13181307
return fmt.Errorf("truncate buffer: %w", err)

vfs_write_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,15 @@ func TestVFSFile_WriteEnabled(t *testing.T) {
229229
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
230230

231231
// Create VFSFile directly with write enabled
232+
tmpDir := t.TempDir()
233+
bufferPath := tmpDir + "/write-buffer"
234+
232235
logger := slog.Default()
233236
f := NewVFSFile(client, "test.db", logger)
234237
f.writeEnabled = true
235238
f.dirty = make(map[uint32]int64)
236239
f.syncInterval = 0
240+
f.bufferPath = bufferPath
237241

238242
if err := f.Open(); err != nil {
239243
t.Fatal(err)
@@ -639,6 +643,54 @@ func TestVFSFile_WriteBufferClearAfterSync(t *testing.T) {
639643
}
640644
}
641645

646+
func TestVFSFile_OpenFailsWithInvalidBufferPath(t *testing.T) {
647+
client := newWriteTestReplicaClient()
648+
649+
pageSize := uint32(4096)
650+
initialPage := make([]byte, pageSize)
651+
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
652+
653+
logger := slog.Default()
654+
f := NewVFSFile(client, "test.db", logger)
655+
f.writeEnabled = true
656+
f.dirty = make(map[uint32]int64)
657+
f.syncInterval = 0
658+
f.bufferPath = "/nonexistent/path/that/cannot/be/created/buffer"
659+
660+
err := f.Open()
661+
if err == nil {
662+
f.Close()
663+
t.Fatal("expected Open to fail with invalid buffer path")
664+
}
665+
}
666+
667+
func TestVFSFile_BufferFileAlwaysCreatedWhenWriteEnabled(t *testing.T) {
668+
client := newWriteTestReplicaClient()
669+
670+
pageSize := uint32(4096)
671+
initialPage := make([]byte, pageSize)
672+
createTestLTXFile(t, client, 1, pageSize, 1, map[uint32][]byte{1: initialPage})
673+
674+
tmpDir := t.TempDir()
675+
bufferPath := tmpDir + "/write-buffer"
676+
677+
logger := slog.Default()
678+
f := NewVFSFile(client, "test.db", logger)
679+
f.writeEnabled = true
680+
f.dirty = make(map[uint32]int64)
681+
f.syncInterval = 0
682+
f.bufferPath = bufferPath
683+
684+
if err := f.Open(); err != nil {
685+
t.Fatal(err)
686+
}
687+
defer f.Close()
688+
689+
if f.bufferFile == nil {
690+
t.Fatal("bufferFile should never be nil when writeEnabled is true")
691+
}
692+
}
693+
642694
func TestVFSFile_OpenNewDatabase(t *testing.T) {
643695
// Test opening a VFSFile with write mode enabled when no LTX files exist (new database)
644696
client := newWriteTestReplicaClient()

0 commit comments

Comments
 (0)