From cf465899b47b9a2d3108a5fcbdbcb1cd6324a27d Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 9 Sep 2025 17:16:23 +0300 Subject: [PATCH] New: Retry SQLite writes for database is locked errors (cherry picked from commit 2e1289b9248a70ce50bde52a66d3a589f3dcb8f5) --- .../Datastore/BasicRepository.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index 8c7e6a13a2..618999992a 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -1,13 +1,18 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.SQLite; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using Dapper; +using NLog; +using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Messaging.Events; +using Polly; +using Polly.Retry; namespace NzbDrone.Core.Datastore { @@ -40,12 +45,31 @@ public interface IBasicRepository public class BasicRepository : IBasicRepository where TModel : ModelBase, new() { + private static readonly ILogger Logger = NzbDroneLogger.GetLogger(typeof(BasicRepository)); + private readonly IEventAggregator _eventAggregator; private readonly PropertyInfo _keyProperty; private readonly List _properties; private readonly string _updateSql; private readonly string _insertSql; + private static ResiliencePipeline RetryStrategy => new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(ex => ex.ResultCode == SQLiteErrorCode.Busy), + Delay = TimeSpan.FromMilliseconds(100), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Exponential, + UseJitter = true, + OnRetry = args => + { + Logger.Warn(args.Outcome.Exception, "Failed writing to database. Retry #{0}", args.AttemptNumber); + + return default; + } + }) + .Build(); + protected readonly IDatabase _database; protected readonly string _table; @@ -186,7 +210,9 @@ private string GetInsertSql() private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model) { SqlBuilderExtensions.LogQuery(_insertSql, model); - var multi = connection.QueryMultiple(_insertSql, model, transaction); + + var multi = RetryStrategy.Execute(static (state, _) => state.connection.QueryMultiple(state._insertSql, state.model, state.transaction), (connection, _insertSql, model, transaction)); + var multiRead = multi.Read(); var id = (int)(multiRead.First().id ?? multiRead.First().Id); _keyProperty.SetValue(model, id); @@ -381,7 +407,7 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction, SqlBuilderExtensions.LogQuery(sql, model); - connection.Execute(sql, model, transaction: transaction); + RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.model, transaction: state.transaction), (connection, sql, model, transaction)); } private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList models, List propertiesToUpdate) @@ -393,7 +419,7 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction, SqlBuilderExtensions.LogQuery(sql, model); } - connection.Execute(sql, models, transaction: transaction); + RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.models, transaction: state.transaction), (connection, sql, models, transaction)); } protected virtual SqlBuilder PagedBuilder() => Builder();