## Summary `sp_HumanEventsBlockViewer` silently drops blocked-process reports that don't form a lock-blocking chain between two **distinct** sessions. Two cases in particular: - **Self-referential reports** — the blocked and blocking process are the same `SPID:ECID` (a session reported as blocked by itself). - **Non-lock waits** — `resource_owner_type = GENERIC` / `lock_mode = NL` (no actual lock). The blocked-process monitor fires for *any* task waiting longer than `blocked process threshold`, not just lock waits, so e.g. a long `RESOURCE_SEMAPHORE` (memory-grant) wait surfaces as a self-referential, non-lock blocked-process report. These carry a real signal — *"spid N waited ~9s on a non-lock resource"* — but today they produce **zero rows**, so a caller can't distinguish "no blocking happened" from "blocking happened but it was a non-lock/self wait we discarded." ## Where it happens The shred populates `#blocked` / `#blocking` from `//blocked-process-report/blocked-process` and `.../blocking-process`, already capturing `resource_owner_type` and `lock_mode` (around lines 1955/1967 and 2075/2087). The hierarchy/chain builder then pairs blocked↔blocking, and the **cycle guard** drops anything that would block itself: ```sql -- ~line 2226 /* Cycle guard: skip a row whose blocked_desc already appears in the accumulated sort_order ... */ WHERE h.sort_order NOT LIKE '%' + bg.blocked_desc + '%' ``` A self-referential report has `blocked_desc = blocking_desc`, so the candidate is excluded immediately and no `#blocks` row is produced. ## Request Add a **separate shred/output path** that surfaces these non-chain reports apart from the normal lock-blocking-chain result — e.g. an extra result set of "non-lock / self waits" carrying `event_time`, database, `spid`, `wait_resource` / `resource_owner_type`, `lock_mode`, `wait_time`, and `inputbuf`, derived from `#blocked` **before** chain reconstruction and filtered to exactly the rows the cycle guard would drop (self-referential `SPID:ECID`, and/or a `resource_owner_type` / `lock_mode` indicating a non-lock wait). The normal chain path stays exactly as-is; this just stops silently discarding the non-chain reports. ## Concrete example (real capture, SQL Server 2017) blocked spid = blocking spid = **61**, `resource_owner_type = GENERIC`, `lock_mode = NL`, `waittime = 9046 ms` — a ~9s memory-grant (`RESOURCE_SEMAPHORE`) wait during a server-wide grant-pressure stall, reported as a session blocked by itself: ```xml <event name="blocked_process_report" timestamp="2026-06-15T04:45:21.621Z"> <data name="lock_mode"><value>0</value><text>NL</text></data> <data name="resource_owner_type"><value>0x00000008</value><text>GENERIC</text></data> <data name="blocked_process"> <value> <blocked-process-report monitorLoop="184"> <blocked-process> <process status="suspended" waittime="9046" spid="61" ecid="0" ... /> </blocked-process> <blocking-process> <process status="suspended" waittime="9034" spid="61" ecid="0" ... /> </blocking-process> </blocked-process-report> </value> </data> </event> ``` (`inputbuf` elided — it was a monitoring collector query that tripped the threshold while waiting for a memory grant.) ## Context Surfaced via a monitoring tool's blocked-process collector that calls `sp_HumanEventsBlockViewer @target_type = N'table'` to parse staged XML. Because these reports parse to zero rows, a downstream processor that only marked events "processed" when rows were produced looped on them indefinitely. That was fixed on the caller side (mark-on-clean-run), but the underlying reports are genuinely informative and worth surfacing rather than dropping.