Public Access
44 lines
1.6 KiB
C#
44 lines
1.6 KiB
C#
using System.Data.Common;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
|
|
namespace DodoSSH.Api.Tests;
|
|
|
|
/// <summary>
|
|
/// Fails the next commit the way a connection dropped on the way to the server would, when asked to.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Before the commit reaches the database, so the transaction is rolled back — which is what a lost commit
|
|
/// looks like from the server's side — and with a <see cref="TimeoutException"/>, which the retrying
|
|
/// execution strategy treats as transient. That is the only way to watch a retry happen against a real
|
|
/// database, and the retry is the thing worth watching: see <c>FreshAttempts</c>.
|
|
/// </remarks>
|
|
internal sealed class FlakyCommits : DbTransactionInterceptor
|
|
{
|
|
private int armed;
|
|
|
|
private int failed;
|
|
|
|
/// <summary>How many commits this has failed.</summary>
|
|
internal int Failed => Volatile.Read(ref failed);
|
|
|
|
/// <summary>Makes the next commit, whoever's it is, fail once.</summary>
|
|
internal void FailNextCommit() => Interlocked.Exchange(ref armed, 1);
|
|
|
|
/// <inheritdoc />
|
|
public override ValueTask<InterceptionResult> TransactionCommittingAsync(
|
|
DbTransaction transaction,
|
|
TransactionEventData eventData,
|
|
InterceptionResult result,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (Interlocked.Exchange(ref armed, 0) == 1)
|
|
{
|
|
Interlocked.Increment(ref failed);
|
|
|
|
throw new TimeoutException("The commit was lost on its way to the server.");
|
|
}
|
|
|
|
return base.TransactionCommittingAsync(transaction, eventData, result, cancellationToken);
|
|
}
|
|
}
|