diff --git a/store.go b/store.go index d1baf39..10137ed 100644 --- a/store.go +++ b/store.go @@ -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")` diff --git a/workspace.go b/workspace.go index 45de1d5..4e4a55c 100644 --- a/workspace.go +++ b/workspace.go @@ -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)) diff --git a/workspace_test.go b/workspace_test.go index d3f04e4..72359d0 100644 --- a/workspace_test.go +++ b/workspace_test.go @@ -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)) +}