From ad6104132d630abc6c45af6030f7de2614f7b918 Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Thu, 26 Mar 2026 09:52:38 -0400 Subject: [PATCH] fix: use transaction for MBID updates to prevent SQLite deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateMBIDs was calling l.db.ExecContext (main connection) while inside a transaction that held the write lock. With SQLite's SetMaxOpenConns(1), this deadlocked — the UPDATE waited for the transaction to release the lock, but the transaction waited for the UPDATE to complete. Fix: pass *sql.Tx through processMetadata to updateMBIDs and use tx.ExecContext instead. All MBID writes now happen within the same transaction as the entity upserts. --- backend/library/library.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/backend/library/library.go b/backend/library/library.go index a7f84f5..dc4b5cc 100644 --- a/backend/library/library.go +++ b/backend/library/library.go @@ -973,7 +973,7 @@ func (l *Library) saveAudioFile( // Process metadata and create related records. recordingID, err := l.processMetadata( - q, cache, metrics, result, thumbChan, + q, tx, cache, metrics, result, thumbChan, ) if err != nil { return fmt.Errorf("could not process metadata: %w", err) @@ -1067,7 +1067,7 @@ func (l *Library) updateAudioFileMetadata( // Process metadata and create related records. recordingID, err := l.processMetadata( - q, cache, metrics, result, thumbChan, + q, tx, cache, metrics, result, thumbChan, ) if err != nil { return fmt.Errorf("could not process metadata: %w", err) @@ -1148,6 +1148,7 @@ func (l *Library) updateAudioFileMetadata( // asynchronously. func (l *Library) processMetadata( q *sqlcgen.Queries, + tx *sql.Tx, cache *entityCache, metrics *ScanMetrics, result importResult, @@ -1236,9 +1237,9 @@ func (l *Library) processMetadata( // 7. Update MusicBrainz IDs (if present in tags). if releaseGroupID.Valid { - l.updateMBIDs(cache, tags, artistName, releaseGroupID.Int64, recording.ID) + l.updateMBIDs(tx, cache, tags, artistName, releaseGroupID.Int64, recording.ID) } else { - l.updateMBIDs(cache, tags, artistName, 0, recording.ID) + l.updateMBIDs(tx, cache, tags, artistName, 0, recording.ID) } return recording.ID, nil @@ -1249,6 +1250,7 @@ func (l *Library) processMetadata( // queries predate the mbid columns. Skips silently if tags have // no MBIDs. func (l *Library) updateMBIDs( + tx *sql.Tx, cache *entityCache, tags *metadata.TrackMetadata, artistName string, @@ -1263,7 +1265,7 @@ func (l *Library) updateMBIDs( if artistMBID != "" { if artist, ok := cache.artists[artistName]; ok { - _, _ = l.db.ExecContext( + _, _ = tx.ExecContext(l.ctx, "UPDATE artists SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')", artistMBID, artist.ID, ) @@ -1272,7 +1274,7 @@ func (l *Library) updateMBIDs( // Release group MBID. if tags.ReleaseGroupMBID != "" && releaseGroupID > 0 { - _, _ = l.db.ExecContext( + _, _ = tx.ExecContext(l.ctx, "UPDATE release_groups SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')", tags.ReleaseGroupMBID, releaseGroupID, ) @@ -1280,7 +1282,7 @@ func (l *Library) updateMBIDs( // Recording MBID. if tags.RecordingMBID != "" && recordingID > 0 { - _, _ = l.db.ExecContext( + _, _ = tx.ExecContext(l.ctx, "UPDATE recordings SET mbid = ? WHERE id = ? AND (mbid IS NULL OR mbid = '')", tags.RecordingMBID, recordingID, )