From 0416103f2690a27b7defa3eafbd4caeb79c3f0b1 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 22 Aug 2025 08:08:06 +0200 Subject: [PATCH] fix(cmd): make database migration more robust to write errors (#10278) Two things: - We could run into a write error, which would block the progress forever without an error. This because the writer routine exited, while the reader was just blocked on sending to it. - After a failed migration, inserts could fail with unique index constraint errors because we are reusing the sequence numbers from the original database. Add a drop folder to the start of migration to handle this. Additionally, the drop folder will clear out broken database files due to killed migrations. --- lib/syncthing/utils.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index f4e8469bc..5e5cb6937 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -197,12 +197,20 @@ func TryMigrateDatabase(deleteRetention time.Duration) error { var writeErr error var wg sync.WaitGroup wg.Add(1) + writerDone := make(chan struct{}) go func() { defer wg.Done() + defer close(writerDone) var batch []protocol.FileInfo files, blocks := 0, 0 t0 := time.Now() t1 := time.Now() + + if writeErr = sdb.DropFolder(folder); writeErr != nil { + slog.Error("Failed database drop", slogutil.Error(writeErr)) + return + } + for fi := range fis { batch = append(batch, fi) files++ @@ -210,6 +218,7 @@ func TryMigrateDatabase(deleteRetention time.Duration) error { if len(batch) == 1000 { writeErr = sdb.Update(folder, protocol.LocalDeviceID, batch) if writeErr != nil { + slog.Error("Failed database write", slogutil.Error(writeErr)) return } batch = batch[:0] @@ -244,8 +253,12 @@ func TryMigrateDatabase(deleteRetention time.Duration) error { // criteria in the database return true } - fis <- fi - return true + select { + case fis <- fi: + return true + case <-writerDone: + return false + } }) close(fis) snap.Release()