Skip to content

Commit c01543b

Browse files
Guard sp_HumanEventsBlockViewer recursive blocking-tree against cycles
The blocking-hierarchy CTE (lines ~2145-2203) used OPTION(MAXRECURSION 0), removing the default safety ceiling entirely. Two sessions can legitimately appear to block each other in the same monitor_loop window — before the deadlock monitor resolves — and the recursive join bg.blocking_desc = h.blocked_desc has no cycle check. Once an anchor row feeds into a cycle in the blocked tree, the CTE has no exit and the UPDATE runs until tempdb fills. Added a guard to the recursive step: WHERE h.sort_order NOT LIKE '%' + bg.blocked_desc + '%' sort_order already accumulates every (SPID:ECID) visited on this branch, so a LIKE check against the candidate blocked_desc breaks the recursion before a revisit. Reverted MAXRECURSION 0 to MAXRECURSION 100 as a backstop in case an unexpected blocked_desc format slips past the LIKE guard — hitting 100 raises a catchable error instead of running unbounded. Verified in isolation: start→a, a→b, b→a (chain into cycle): Without guard, MAXRECURSION 100: "maximum recursion ... exhausted" With guard: two rows returned (start→a at level 0, a→b at level 1), the b→a edge that would close the cycle is correctly skipped Normal chain 1→2→3→4: three rows, all levels populated (regression check) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ca2dbd8 commit c01543b

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

sp_HumanEvents/sp_HumanEventsBlockViewer.sql

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,15 @@ WITH
21892189
JOIN #blocking AS bg
21902190
ON bg.monitor_loop = h.monitor_loop
21912191
AND bg.blocking_desc = h.blocked_desc
2192+
/*
2193+
Cycle guard: skip a row whose blocked_desc already appears in the
2194+
accumulated sort_order. Two sessions can briefly appear to block each
2195+
other in the same monitor_loop (before the deadlock monitor fires),
2196+
and without a guard the recursion has no exit. The sort_order string
2197+
contains every (SPID:ECID) we've visited on this branch; checking for
2198+
the candidate blocked_desc before we follow it prevents the cycle.
2199+
*/
2200+
WHERE h.sort_order NOT LIKE '%' + bg.blocked_desc + '%'
21922201
)
21932202
UPDATE
21942203
#blocked
@@ -2200,7 +2209,13 @@ JOIN hierarchy AS h
22002209
ON h.monitor_loop = b.monitor_loop
22012210
AND h.blocking_desc = b.blocking_desc
22022211
AND h.blocked_desc = b.blocked_desc
2203-
OPTION(RECOMPILE, MAXRECURSION 0);
2212+
/*
2213+
MAXRECURSION 100 (the default) is plenty for real blocking chains and
2214+
still acts as a backstop if the cycle guard above is ever bypassed by
2215+
a blocked_desc that doesn't format the same way as expected. Reverted
2216+
from MAXRECURSION 0 which gave the runaway case no ceiling at all.
2217+
*/
2218+
OPTION(RECOMPILE, MAXRECURSION 100);
22042219

22052220
IF @debug = 1
22062221
BEGIN

0 commit comments

Comments
 (0)