fix(store): preserve orphan files for recovery

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-04 08:12:29 +00:00
parent 7d3b62086d
commit e1cb275578
3 changed files with 47 additions and 3 deletions

View file

@ -258,19 +258,22 @@ func (storeInstance *Store) Close() error {
storeInstance.callbacksLock.Unlock()
storeInstance.orphanWorkspacesLock.Lock()
var orphanCleanupErr error
for _, orphanWorkspace := range storeInstance.orphanWorkspaces {
orphanWorkspace.Discard()
if err := orphanWorkspace.closeWithoutRemovingFiles(); err != nil && orphanCleanupErr == nil {
orphanCleanupErr = err
}
}
storeInstance.orphanWorkspaces = nil
storeInstance.orphanWorkspacesLock.Unlock()
if storeInstance.database == nil {
return nil
return orphanCleanupErr
}
if err := storeInstance.database.Close(); err != nil {
return core.E("store.Close", "database close", err)
}
return nil
return orphanCleanupErr
}
// Usage example: `colourValue, err := storeInstance.Get("config", "colour")`

View file

@ -354,6 +354,16 @@ func (workspace *Workspace) aggregateFields() (map[string]any, error) {
}
func (workspace *Workspace) closeAndRemoveFiles() error {
return workspace.closeAndCleanup(true)
}
// closeWithoutRemovingFiles closes the database handle but leaves the orphan
// file on disk so a later store instance can recover it.
func (workspace *Workspace) closeWithoutRemovingFiles() error {
return workspace.closeAndCleanup(false)
}
func (workspace *Workspace) closeAndCleanup(removeFiles bool) error {
if workspace == nil {
return nil
}
@ -372,6 +382,9 @@ func (workspace *Workspace) closeAndRemoveFiles() error {
if err := workspace.database.Close(); err != nil {
return core.E("store.Workspace.closeAndRemoveFiles", "close workspace database", err)
}
if !removeFiles {
return nil
}
for _, path := range []string{workspace.databasePath, workspace.databasePath + "-wal", workspace.databasePath + "-shm"} {
if result := workspace.filesystem.Delete(path); !result.OK && workspace.filesystem.Exists(path) {
return core.E("store.Workspace.closeAndRemoveFiles", "delete workspace file", result.Value.(error))

View file

@ -246,3 +246,31 @@ func TestWorkspace_New_Good_CachesOrphansDuringConstruction(t *testing.T) {
assert.Equal(t, map[string]any{}, orphans[0].Aggregate())
orphans[0].Discard()
}
func TestWorkspace_Close_Good_PreservesOrphansForRecovery(t *testing.T) {
stateDirectory := useWorkspaceStateDirectory(t)
requireCoreOK(t, testFilesystem().EnsureDir(stateDirectory))
orphanDatabasePath := workspaceFilePath(stateDirectory, "orphan-session")
orphanDatabase, err := openWorkspaceDatabase(orphanDatabasePath)
require.NoError(t, err)
require.NoError(t, orphanDatabase.Close())
assert.True(t, testFilesystem().Exists(orphanDatabasePath))
storeInstance, err := New(":memory:")
require.NoError(t, err)
require.NoError(t, storeInstance.Close())
assert.True(t, testFilesystem().Exists(orphanDatabasePath))
recoveryStore, err := New(":memory:")
require.NoError(t, err)
defer recoveryStore.Close()
orphans := recoveryStore.RecoverOrphans(stateDirectory)
require.Len(t, orphans, 1)
assert.Equal(t, "orphan-session", orphans[0].Name())
orphans[0].Discard()
assert.False(t, testFilesystem().Exists(orphanDatabasePath))
}