< Summary - Jellyfin

Information
Class: Jellyfin.Database.Implementations.Locking.OptimisticLockBehavior
Assembly: Jellyfin.Database.Implementations
File(s): /srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 31
Coverable lines: 31
Total lines: 137
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Initialise(...)100%210%
OnSaveChanges(...)100%210%
.ctor(...)100%210%
TransactionStarting(...)100%210%
.ctor(...)100%210%
NonQueryExecuting(...)100%210%
ScalarExecuting(...)100%210%
ReaderExecuting(...)100%210%

File(s)

/srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs

#LineLine coverage
 1using System;
 2using System.Data.Common;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Microsoft.EntityFrameworkCore;
 7using Microsoft.EntityFrameworkCore.Diagnostics;
 8using Microsoft.Extensions.Logging;
 9using Polly;
 10
 11namespace Jellyfin.Database.Implementations.Locking;
 12
 13/// <summary>
 14/// Defines a locking mechanism that will retry any write operation for a few times.
 15/// </summary>
 16public class OptimisticLockBehavior : IEntityFrameworkCoreLockingBehavior
 17{
 18    private readonly Policy _writePolicy;
 19    private readonly AsyncPolicy _writeAsyncPolicy;
 20    private readonly ILogger<OptimisticLockBehavior> _logger;
 21
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="OptimisticLockBehavior"/> class.
 24    /// </summary>
 25    /// <param name="logger">The application logger.</param>
 26    public OptimisticLockBehavior(ILogger<OptimisticLockBehavior> logger)
 27    {
 028        TimeSpan[] sleepDurations = [
 029            TimeSpan.FromMilliseconds(50),
 030            TimeSpan.FromMilliseconds(50),
 031            TimeSpan.FromMilliseconds(250),
 032            TimeSpan.FromMilliseconds(150),
 033            TimeSpan.FromMilliseconds(500),
 034            TimeSpan.FromMilliseconds(500),
 035            TimeSpan.FromSeconds(3)
 036        ];
 037        _logger = logger;
 038        _writePolicy = Policy.HandleInner<Exception>(e => e.Message.Contains("database is locked", StringComparison.Inva
 039        _writeAsyncPolicy = Policy.HandleInner<Exception>(e => e.Message.Contains("database is locked", StringComparison
 40
 41        void RetryHandle(Exception exception, TimeSpan timespan, int retryNo, Context context)
 42        {
 43            if (retryNo < sleepDurations.Length)
 44            {
 45                _logger.LogWarning("Operation failed retry {RetryNo}", retryNo);
 46            }
 47            else
 48            {
 49                _logger.LogError(exception, "Operation failed retry {RetryNo}", retryNo);
 50            }
 51        }
 052    }
 53
 54    /// <inheritdoc/>
 55    public void Initialise(DbContextOptionsBuilder optionsBuilder)
 56    {
 057        _logger.LogInformation("The database locking mode has been set to: Optimistic.");
 058        optionsBuilder.AddInterceptors(new RetryInterceptor(_writeAsyncPolicy, _writePolicy));
 059        optionsBuilder.AddInterceptors(new TransactionLockingInterceptor(_writeAsyncPolicy, _writePolicy));
 060    }
 61
 62    /// <inheritdoc/>
 63    public void OnSaveChanges(JellyfinDbContext context, Action saveChanges)
 64    {
 065        _writePolicy.ExecuteAndCapture(saveChanges);
 066    }
 67
 68    /// <inheritdoc/>
 69    public async Task OnSaveChangesAsync(JellyfinDbContext context, Func<Task> saveChanges)
 70    {
 71        await _writeAsyncPolicy.ExecuteAndCaptureAsync(saveChanges).ConfigureAwait(false);
 72    }
 73
 74    private sealed class TransactionLockingInterceptor : DbTransactionInterceptor
 75    {
 76        private readonly AsyncPolicy _asyncRetryPolicy;
 77        private readonly Policy _retryPolicy;
 78
 079        public TransactionLockingInterceptor(AsyncPolicy asyncRetryPolicy, Policy retryPolicy)
 80        {
 081            _asyncRetryPolicy = asyncRetryPolicy;
 082            _retryPolicy = retryPolicy;
 083        }
 84
 85        public override InterceptionResult<DbTransaction> TransactionStarting(DbConnection connection, TransactionStarti
 86        {
 087            return InterceptionResult<DbTransaction>.SuppressWithResult(_retryPolicy.Execute(() => connection.BeginTrans
 88        }
 89
 90        public override async ValueTask<InterceptionResult<DbTransaction>> TransactionStartingAsync(DbConnection connect
 91        {
 92            return InterceptionResult<DbTransaction>.SuppressWithResult(await _asyncRetryPolicy.ExecuteAsync(async () =>
 93        }
 94    }
 95
 96    private sealed class RetryInterceptor : DbCommandInterceptor
 97    {
 98        private readonly AsyncPolicy _asyncRetryPolicy;
 99        private readonly Policy _retryPolicy;
 100
 0101        public RetryInterceptor(AsyncPolicy asyncRetryPolicy, Policy retryPolicy)
 102        {
 0103            _asyncRetryPolicy = asyncRetryPolicy;
 0104            _retryPolicy = retryPolicy;
 0105        }
 106
 107        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, Interce
 108        {
 0109            return InterceptionResult<int>.SuppressWithResult(_retryPolicy.Execute(command.ExecuteNonQuery));
 110        }
 111
 112        public override async ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventD
 113        {
 114            return InterceptionResult<int>.SuppressWithResult(await _asyncRetryPolicy.ExecuteAsync(async () => await com
 115        }
 116
 117        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, Interc
 118        {
 0119            return InterceptionResult<object>.SuppressWithResult(_retryPolicy.Execute(() => command.ExecuteScalar()!));
 120        }
 121
 122        public override async ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEvent
 123        {
 124            return InterceptionResult<object>.SuppressWithResult((await _asyncRetryPolicy.ExecuteAsync(async () => await
 125        }
 126
 127        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, 
 128        {
 0129            return InterceptionResult<DbDataReader>.SuppressWithResult(_retryPolicy.Execute(command.ExecuteReader));
 130        }
 131
 132        public override async ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, Comman
 133        {
 134            return InterceptionResult<DbDataReader>.SuppressWithResult(await _asyncRetryPolicy.ExecuteAsync(async () => 
 135        }
 136    }
 137}