From a5741050df20228196ac5fbbef89d9acf97381a5 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 26 Feb 2025 21:29:53 -0500 Subject: [PATCH] fix(server): encapsulated way to upgrade tx to write mode Signed-off-by: Deluan --- model/datastore.go | 1 + persistence/persistence.go | 14 ++++++++++++++ server/subsonic/media_annotation.go | 11 +---------- tests/mock_data_store.go | 4 ++++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/model/datastore.go b/model/datastore.go index 3babb9f1b..4290e2134 100644 --- a/model/datastore.go +++ b/model/datastore.go @@ -42,5 +42,6 @@ type DataStore interface { Resource(ctx context.Context, model interface{}) ResourceRepository WithTx(block func(tx DataStore) error, scope ...string) error + WithTxImmediate(block func(tx DataStore) error, scope ...string) error GC(ctx context.Context) error } diff --git a/persistence/persistence.go b/persistence/persistence.go index d9569c4d0..579f13707 100644 --- a/persistence/persistence.go +++ b/persistence/persistence.go @@ -143,6 +143,20 @@ func (s *SQLStore) WithTx(block func(tx model.DataStore) error, scope ...string) }) } +func (s *SQLStore) WithTxImmediate(block func(tx model.DataStore) error, scope ...string) error { + ctx := context.Background() + return s.WithTx(func(tx model.DataStore) error { + // Workaround to force the transaction to be upgraded to immediate mode to avoid deadlocks + // See https://berthub.eu/articles/posts/a-brief-post-on-sqlite3-database-locked-despite-timeout/ + _ = tx.Property(ctx).Put("tmp_lock_flag", "") + defer func() { + _ = tx.Property(ctx).Delete("tmp_lock_flag") + }() + + return block(tx) + }, scope...) +} + func (s *SQLStore) GC(ctx context.Context) error { trace := func(ctx context.Context, msg string, f func() error) func() error { return func() error { diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index db53ab134..74000856f 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -6,7 +6,6 @@ import ( "net/http" "time" - "github.com/google/uuid" "github.com/navidrome/navidrome/core/scrobbler" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" @@ -113,15 +112,7 @@ func (api *Router) setStar(ctx context.Context, star bool, ids ...string) error return nil } event := &events.RefreshResource{} - err := api.ds.WithTx(func(tx model.DataStore) error { - // Workaround to force the transaction to be upgraded to immediate mode to avoid deadlocks - // See https://berthub.eu/articles/posts/a-brief-post-on-sqlite3-database-locked-despite-timeout/ - tmpID := uuid.NewString() - _ = tx.Property(ctx).Put("tmp_"+tmpID, "") - defer func() { - _ = tx.Property(ctx).Delete("tmp_" + tmpID) - }() - + err := api.ds.WithTxImmediate(func(tx model.DataStore) error { for _, id := range ids { exist, err := tx.Album(ctx).Exists(id) if err != nil { diff --git a/tests/mock_data_store.go b/tests/mock_data_store.go index e5f7cd8b6..f380755e0 100644 --- a/tests/mock_data_store.go +++ b/tests/mock_data_store.go @@ -213,6 +213,10 @@ func (db *MockDataStore) WithTx(block func(tx model.DataStore) error, label ...s return block(db) } +func (db *MockDataStore) WithTxImmediate(block func(tx model.DataStore) error, label ...string) error { + return block(db) +} + func (db *MockDataStore) Resource(context.Context, any) model.ResourceRepository { return struct{ model.ResourceRepository }{} }