sqlinsight-pro/src/SQLInsight.Core/Data/Deadlocks.cs
Søren Eggert Lundsteen Olsen 25a67cb98b Stop SQLInsight deadlocking itself, and stop reporting its own deadlocks
At a customer whose repository sits on a monitored instance, the agent
reported 12 deadlocks that were its own: two instances' monitors save
findings in parallel, one transaction each, and while insight.Findings is
small SQL Server scans it, so the two lock each other's rows.

- SaveRuleResultsAsync takes an exclusive application lock for its
  transaction (the agent is the only writer; a save takes milliseconds)
  and retries if chosen as deadlock victim anyway
- repository connections are named "SQLInsight Pro (repository)"
- deadlocks where every participant is SQLInsight Pro are not reported
  (the read position still moves past them); any other session keeps it

The new integration test (two instances saving 25 rules x 40 rounds in
parallel) failed 2 of 2 without the fix and passed 5 of 5 with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 07:34:59 +02:00

54 lines
2.2 KiB
C#

namespace SQLInsight.Core.Data;
/// <summary>
/// Deadlocks captured by the <c>system_health</c> Extended Events session since the previous collection.
/// </summary>
public sealed record DeadlockSnapshot
{
public required DateTimeOffset CapturedAt { get; init; }
/// <summary>Deadlocks newer than the previous collection.</summary>
public required IReadOnlyList<DeadlockEvent> NewDeadlocks { get; init; }
/// <summary>Newest event time seen so far; the next collection starts after it.</summary>
public DateTimeOffset? LastEventTime { get; init; }
/// <summary>Where reading stopped in the event file, so the next read does not rescan all files.</summary>
public string? LastFileName { get; init; }
public long? LastFileOffset { get; init; }
}
public sealed record DeadlockEvent
{
public required DateTimeOffset Time { get; init; }
public required IReadOnlyList<DeadlockProcess> Processes { get; init; }
/// <summary>The raw deadlock graph (XDL), for download and viewing in SSMS.</summary>
public required string GraphXml { get; init; }
/// <summary>
/// Every participant is SQLInsight Pro itself (monitoring or its own repository), e.g. when the repository lives on
/// a monitored instance. Not the customer's problem, so not reported. A deadlock with any other session is kept.
/// </summary>
public bool IsOwn => Processes.Count > 0
&& Processes.All(p => p.ClientApp?.StartsWith(OwnApplicationName, StringComparison.Ordinal) == true);
/// <summary>Application Name prefix on all of SQLInsight Pro's connections ("SQLInsight Pro", "SQLInsight Pro (repository)").</summary>
public const string OwnApplicationName = "SQLInsight Pro";
}
public sealed record DeadlockProcess
{
public required string Id { get; init; }
public bool IsVictim { get; init; }
public int? SessionId { get; init; }
public string? DatabaseName { get; init; }
public string? LoginName { get; init; }
public string? HostName { get; init; }
public string? ClientApp { get; init; }
public string? IsolationLevel { get; init; }
public string? WaitResource { get; init; }
public string? LockMode { get; init; }
public string? InputBuffer { get; init; }
}