fix: implement exponential backoff strategy for concurrency exceptions in SaveChangesAsync

This commit is contained in:
2026-07-07 11:31:47 -04:00
parent 9bcb8501ab
commit 421a8cc092
@@ -352,18 +352,36 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
}
catch (DbUpdateConcurrencyException ex)
{
// When marking items as played in quick succession, concurrency conflicts can occur.
// To resolve this, we'll reload the conflicting entities from the database and retry.
logger.LogWarning(ex, "Concurrency exception, retrying operation.");
// Concurrency conflicts can occur when marking items as played in quick succession.
// To resolve this, we'll retry the operation with an exponential backoff strategy.
logger.LogWarning(ex, "Concurrency exception, retrying operation with exponential backoff.");
foreach (var entry in ex.Entries)
var maxRetries = 3;
var delay = TimeSpan.FromMilliseconds(100);
for (var i = 0; i < maxRetries; i++)
{
// Reload the entity from the database to get the latest version
await entry.ReloadAsync(cancellationToken).ConfigureAwait(false);
try
{
foreach (var entry in ex.Entries)
{
// Reload the entity from the database to get the latest version
await entry.ReloadAsync(cancellationToken).ConfigureAwait(false);
}
// Retry saving changes after resolving conflicts
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false);
}
catch (DbUpdateConcurrencyException retryEx) when (i < maxRetries - 1)
{
logger.LogWarning(retryEx, "Concurrency exception on retry {RetryCount}, delaying for {Delay}ms.", i + 1, delay.TotalMilliseconds);
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
delay *= 2; // Exponential backoff
}
}
// Retry saving changes after resolving conflicts
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false);
// If all retries fail, rethrow the last exception
throw;
}
catch (DbUpdateException ex)
{