#pragma warning disable MT1013 // Releasing lock without guarantee of execution
#pragma warning disable MT1012 // Acquiring lock without guarantee of releasing
using System;
using System.Data;
using System.Data.Common;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Database.Implementations.Locking;
///
/// A locking behavior that will always block any operation while a write is requested. Mimicks the old SqliteRepository behavior.
///
public class PessimisticLockBehavior : IEntityFrameworkCoreLockingBehavior
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
///
/// Initializes a new instance of the class.
///
/// The application logger.
/// The logger factory.
public PessimisticLockBehavior(ILogger logger, ILoggerFactory loggerFactory)
{
_logger = logger;
_loggerFactory = loggerFactory;
}
private static ReaderWriterLockSlim DatabaseLock { get; } = new(LockRecursionPolicy.SupportsRecursion);
///
public void OnSaveChanges(JellyfinDbContext context, Action saveChanges)
{
using (DbLock.EnterWrite(_logger))
{
saveChanges();
}
}
///
public void Initialise(DbContextOptionsBuilder optionsBuilder)
{
_logger.LogInformation("The database locking mode has been set to: Pessimistic.");
optionsBuilder.AddInterceptors(new CommandLockingInterceptor(_loggerFactory.CreateLogger()));
optionsBuilder.AddInterceptors(new TransactionLockingInterceptor(_loggerFactory.CreateLogger()));
}
///
public async Task OnSaveChangesAsync(JellyfinDbContext context, Func saveChanges)
{
using (DbLock.EnterWrite(_logger))
{
await saveChanges().ConfigureAwait(false);
}
}
private sealed class TransactionLockingInterceptor : DbTransactionInterceptor
{
private readonly ILogger _logger;
public TransactionLockingInterceptor(ILogger logger)
{
_logger = logger;
}
public override InterceptionResult TransactionStarting(DbConnection connection, TransactionStartingEventData eventData, InterceptionResult result)
{
DbLock.BeginWriteLock(_logger);
return base.TransactionStarting(connection, eventData, result);
}
public override ValueTask> TransactionStartingAsync(DbConnection connection, TransactionStartingEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
DbLock.BeginWriteLock(_logger);
return base.TransactionStartingAsync(connection, eventData, result, cancellationToken);
}
public override void TransactionCommitted(DbTransaction transaction, TransactionEndEventData eventData)
{
DbLock.EndWriteLock(_logger);
base.TransactionCommitted(transaction, eventData);
}
public override Task TransactionCommittedAsync(DbTransaction transaction, TransactionEndEventData eventData, CancellationToken cancellationToken = default)
{
DbLock.EndWriteLock(_logger);
return base.TransactionCommittedAsync(transaction, eventData, cancellationToken);
}
public override void TransactionFailed(DbTransaction transaction, TransactionErrorEventData eventData)
{
DbLock.EndWriteLock(_logger);
base.TransactionFailed(transaction, eventData);
}
public override Task TransactionFailedAsync(DbTransaction transaction, TransactionErrorEventData eventData, CancellationToken cancellationToken = default)
{
DbLock.EndWriteLock(_logger);
return base.TransactionFailedAsync(transaction, eventData, cancellationToken);
}
public override void TransactionRolledBack(DbTransaction transaction, TransactionEndEventData eventData)
{
DbLock.EndWriteLock(_logger);
base.TransactionRolledBack(transaction, eventData);
}
public override Task TransactionRolledBackAsync(DbTransaction transaction, TransactionEndEventData eventData, CancellationToken cancellationToken = default)
{
DbLock.EndWriteLock(_logger);
return base.TransactionRolledBackAsync(transaction, eventData, cancellationToken);
}
}
///
/// Adds strict read/write locking.
///
private sealed class CommandLockingInterceptor : DbCommandInterceptor
{
private readonly ILogger _logger;
public CommandLockingInterceptor(ILogger logger)
{
_logger = logger;
}
public override InterceptionResult NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result)
{
using (DbLock.EnterWrite(_logger, command))
{
return InterceptionResult.SuppressWithResult(command.ExecuteNonQuery());
}
}
public override async ValueTask> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
using (DbLock.EnterWrite(_logger, command))
{
return InterceptionResult.SuppressWithResult(await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false));
}
}
public override InterceptionResult