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>
54 lines
2.2 KiB
C#
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; }
|
|
}
|