Skip to content

Abandon-path forensics record target_session_id = 0 instead of the real SPID (or NULL) #2884

Closed

Description

@erikdarlingdata

Summary

Every ABANDONED row written by the V109 forensics (#2864) so far records target_session_id = 0 — not a valid SPID — while the other four forensic columns populate correctly. Three consecutive production abandonments on the same collector all show it:

05:33:02  rows_read=135  bytes=39,484,998  last_read=119,953  peer_max=189   spid=0
05:26:20  rows_read=103  bytes=25,400,838  last_read=115,335  peer_max=1315  spid=0
04:31:06  rows_read=119  bytes=33,226,792  last_read=122,249  peer_max=226   spid=0

Successful runs DO record real session ids (hundreds per 5-minute window on all three stores within minutes of the deploy), so the write path works in general — the zero appears specifically on the abandon path.

Why it matters

The session id exists precisely for the retrospective join on a stalled run — matching the abandoned cycle to waiting_tasks / query_snapshots rows captured by other sweeps. The abandon path is the one place the value is load-bearing, and it is the one place it reads 0.

Likely shape

#2868 captures the id off the open connection as a client property (SqlConnection.ServerProcessId / NpgsqlConnection.ProcessID) rather than querying for it. Candidate causes worth checking rather than assuming: the property being read from a connection object already in a broken/doomed state at abandon time; the value being captured lazily at write time instead of at open; or the provider returning default(int) when the connection has been cancelled. The V109 review already withdrew one over-claim about NULL semantics on this path (budget firing inside ExecuteReaderAsync before the counting reader exists) — this may be the sibling case for the session id, in which case the honest fix could be writing NULL (not-recorded) rather than a literal 0, which reads as a real-looking-but-wrong value.

0 is worse than NULL here: NULL means not-recorded by declared convention; 0 looks like data.

Activity

  1. added a commit that references this issue on Sep 4, 2026
    6b1abf7
  2. erikdarlingdata commented on Sep 4, 2026

    @erikdarlingdata
    OwnerAuthor

    Fixed by #2886, merged to `dev` as `6b1abf75`.

    Root cause: the capture sat before `ExecuteReaderAsync`, and `SqlConnection.ServerProcessId` isn't reliably populated until the connection has round-tripped a command — a connection that hadn't yet completed that exchange reported the provider's not-populated 0. The write side also guarded every other forensic figure (`>= 0`) but passed the session id through bare.

    Fix: capture moved to after the open round trip (so a drain-stall abandon now records its real id — the case the column was built for), the helper normalizes non-positive values to null at the source, and the writer grows the sibling-shaped `spid > 0` guard.

    Verified three ways, each proven red against the unfixed source: a source-order pin (capture must follow `ExecuteReaderAsync`), the helper's normalization, and the writer's guard — plus a behavioral check on the built assembly (a never-opened `SqlConnection` maps to null on the fix, leaked 0 without it). All six PR checks green against the merged head SHA, zero inline review findings.

    `Fixes #N` only fires on the dev→main release merge, so closing manually per house policy.

  3. added a commit that references this issue on Sep 10, 2026
    9afc839
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions