diff options
Diffstat (limited to 'internal/persistence/txn.go')
| -rw-r--r-- | internal/persistence/txn.go | 13 |
1 files changed, 12 insertions, 1 deletions
diff --git a/internal/persistence/txn.go b/internal/persistence/txn.go index 7d86cc8..93d186a 100644 --- a/internal/persistence/txn.go +++ b/internal/persistence/txn.go @@ -11,6 +11,11 @@ package persistence // Transactions do NOT serialize concurrent callers: like single-file saves, // they must be executed by an owner that prevents concurrent mutations of // the same files (the game's owner goroutine). +// +// On apply failure the applied operations are rolled back and the journal is +// removed. If the ROLLBACK itself fails, the journal is deliberately kept: +// its before-images are then the only durable record of the pre-transaction +// state, and the next Recover retries the rollback. import ( "crypto/rand" @@ -184,7 +189,13 @@ func (t *Transaction) Commit() error { if err := t.apply(); err != nil { if rbErr := t.rollback(); rbErr != nil { - err = fmt.Errorf("%v; rollback failed: %v", err, rbErr) + // The rollback could not restore the before-images: the journal + // is now the only durable record of the pre-transaction state. + // KEEP it so the next Recover call (or operator repair of the + // underlying fault) can retry the rollback instead of silently + // losing the before-images forever. See + // TestCommitKeepsJournalAfterFailedRollback. + return fmt.Errorf("%v; rollback failed (journal kept for recovery: %s): %v", err, journalPath, rbErr) } if rErr := os.Remove(journalPath); rErr != nil && !os.IsNotExist(rErr) { err = fmt.Errorf("%v; journal cleanup failed: %v", err, rErr) |
