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>
86 lines
3.5 KiB
C#
86 lines
3.5 KiB
C#
using System.Xml.Linq;
|
||
using SQLInsight.Collectors.Concurrency;
|
||
|
||
namespace SQLInsight.Collectors.Tests;
|
||
|
||
public class DeadlockParserTests
|
||
{
|
||
private const string Graph = """
|
||
<deadlock>
|
||
<victim-list><victimProcess id="process1" /></victim-list>
|
||
<process-list>
|
||
<process id="process1" spid="60" waitresource="KEY: 5:72057594043236352 (8194443284a0)" lockMode="U" isolationlevel="read committed (2)"
|
||
clientapp="Sales.Api" hostname="WEB01" loginname="app" currentdb="5" currentdbname="Sales">
|
||
<inputbuf> UPDATE dbo.Orders SET Status = 2 WHERE Id = @id </inputbuf>
|
||
</process>
|
||
<process id="process2" spid="61" waitresource="KEY: 5:72057594043236352 (61a06abd401c)" lockMode="U" currentdb="5">
|
||
<inputbuf>UPDATE dbo.Orders SET Status = 3 WHERE Id = @id</inputbuf>
|
||
</process>
|
||
</process-list>
|
||
<resource-list />
|
||
</deadlock>
|
||
""";
|
||
|
||
[Fact]
|
||
public void Parses_event_file_format()
|
||
{
|
||
var evt = XElement.Parse($"""
|
||
<event name="xml_deadlock_report" timestamp="2026-09-29T10:15:30.123Z">
|
||
<data name="xml_report"><value>{Graph}</value></data>
|
||
</event>
|
||
""");
|
||
|
||
var deadlock = DeadlockParser.ParseEvent(evt)!;
|
||
|
||
Assert.Equal(new DateTimeOffset(2026, 9, 29, 10, 15, 30, 123, TimeSpan.Zero), deadlock.Time);
|
||
Assert.Equal(2, deadlock.Processes.Count);
|
||
var victim = Assert.Single(deadlock.Processes, p => p.IsVictim);
|
||
Assert.Equal(60, victim.SessionId);
|
||
Assert.Equal("Sales", victim.DatabaseName);
|
||
Assert.Equal("Sales.Api", victim.ClientApp);
|
||
Assert.Equal("UPDATE dbo.Orders SET Status = 2 WHERE Id = @id", victim.InputBuffer);
|
||
Assert.Equal("5", deadlock.Processes[1].DatabaseName); // no currentdbname on older builds
|
||
Assert.StartsWith("<deadlock>", deadlock.GraphXml, StringComparison.Ordinal);
|
||
}
|
||
|
||
[Fact]
|
||
public void Parses_sql2008_escaped_graph()
|
||
{
|
||
// SQL Server 2008 RTM–SP1 put the graph in the ring buffer as escaped text.
|
||
var escaped = System.Security.SecurityElement.Escape(Graph);
|
||
var evt = XElement.Parse($"""
|
||
<event name="xml_deadlock_report" timestamp="2026-09-29T10:15:30Z">
|
||
<data name="xml_report"><type name="unicode_string" /><value>{escaped}</value></data>
|
||
</event>
|
||
""");
|
||
|
||
var deadlock = DeadlockParser.ParseEvent(evt)!;
|
||
|
||
Assert.Equal(2, deadlock.Processes.Count);
|
||
}
|
||
|
||
[Fact]
|
||
public void Garbage_is_ignored()
|
||
{
|
||
var evt = XElement.Parse("""<event name="xml_deadlock_report" timestamp="2026-09-29T10:15:30Z"><data name="xml_report"><value>not xml</value></data></event>""");
|
||
|
||
Assert.Null(DeadlockParser.ParseEvent(evt));
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData("SQLInsight Pro (repository)", "SQLInsight Pro (repository)", true)]
|
||
[InlineData("SQLInsight Pro", "SQLInsight Pro (repository)", true)]
|
||
[InlineData("SQLInsight Pro (repository)", "Sales.Api", false)]
|
||
[InlineData("SQLInsight Pro (repository)", null, false)]
|
||
public void Deadlocks_between_sqlinsights_own_sessions_are_recognised(string first, string? second, bool own)
|
||
{
|
||
var deadlock = new SQLInsight.Core.Data.DeadlockEvent
|
||
{
|
||
Time = DateTimeOffset.UnixEpoch,
|
||
GraphXml = "<deadlock />",
|
||
Processes = [new() { Id = "p1", ClientApp = first }, new() { Id = "p2", ClientApp = second }],
|
||
};
|
||
|
||
Assert.Equal(own, deadlock.IsOwn);
|
||
}
|
||
}
|