Skip to content

feat(bigtable): state-based classification for abnormal session close - #20243

Merged
sushanb merged 3 commits into
googleapis:mainfrom
sushanb:feat/bigtable-state-based-abnormal-close
Jul 28, 2026
Merged

feat(bigtable): state-based classification for abnormal session close#20243
sushanb merged 3 commits into
googleapis:mainfrom
sushanb:feat/bigtable-state-based-abnormal-close

Conversation

@sushanb

@sushanb sushanb commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Bring the Go client's abnormal session-close classification in line with Java by keying on the pre-close State, and stamp a StreamEnd:<code> close-reason so sessionz surfaces exit codes that would otherwise land in the "Unspecified" bucket.

  • refactor(bigtable): state-based abnormal-close classification (Java parity)noteAbnormalCloseIfAny now consults Session.prevStateAtClose. Any session whose prior state was NOT StateWaitServerClose counts as abnormal; sessions that gracefully entered WAIT_SERVER_CLOSE (client-initiated CloseSession → server ACK) do not. Matches Java's SessionImpl.handleAbnormalClose gating on prevState != WAIT_SERVER_CLOSE. Heartbeat-miss sessions (ForceClose from Readyprev=Ready) correctly still count as abnormal.
  • observe(bigtable): stamp StreamEnd:<code> close-reason in handleClose — the terminal handleClose path CAS-stamps a StreamEnd:<grpc-code> reason so a bare stream error no longer renders as "Unspecified" on sessionz. Upstream stampers (GoAway, MissedHeartbeat, Error, User) still win the CAS because setCloseReason is one-shot — they run first and this stamp is a strict fallback.
  • fix(bigtable): special-case io.EOF in StreamEnd close-reason — Igor caught during review: status.Code(io.EOF) == codes.Unknown, so the default arm rendered a graceful server-side shutdown as \"StreamEnd:Unknown\" — hiding a distinct-and-common signal. Special-cases errors.Is(err, io.EOF)\"StreamEnd:EOF\" before falling through.

Test plan

  • go test ./bigtable/internal/transport/ -count=1 -race -short -timeout=120s
  • Manual sessionz check: run a workload where the server GOAWAYs a session; confirm close_reason renders as GoAway (not StreamEnd:EOF), i.e. the upstream GoAway stamper still wins the CAS.
  • Manual sessionz check: run a workload where an in-flight session sees a plain graceful stream close; confirm close_reason = StreamEnd:EOF (not Unspecified, not StreamEnd:Unknown).
  • Heartbeat-miss path: kill a session's inbound frames; confirm MissedHeartbeat still wins the CAS (this PR doesn't change that path, but verify no regression).

Reviewer notes

Three reviewers ran clean: session-reviewer (behavioral — 4 specs), session-component-review (boundaries — Part B + Part C), igor-reviewer (persona). No spec drift; setCloseReason remains CAS-once and no upstream stampers are demoted.

sushanb added 3 commits July 28, 2026 17:31
…arity)

Move the consecutive-failure counter's abnormal-close gate from a
reason-string whitelist to the state history captured at the terminal
transitionTo(StateClosed) sites. Matches SessionPoolImpl.java's
`if (prevState != WAIT_SERVER_CLOSE) { consecutiveFailures++; ... }`.

Changes:
- Session gains prevStateAtClose atomic.Int32, stamped once at the two
  transitionTo(StateClosed, ...) call sites (ForceClose + handleClose).
- SessionPoolImpl.noteAbnormalCloseIfAny now checks
  `prevStateAtClose == StateWaitServerClose` — clean-close (Close() →
  WSC → server ack → Closed) is exempt; every other terminal transition
  (server GoAway, heartbeat trip, stream error on a Ready session, or
  never-activated open failure) counts as abnormal. Also emits
  tagSessionAbnormalClose on the counted path for per-abnormal-close
  volume visibility in debug-tag counters.
- Delete isAbnormalCloseReason() and its call in handleClose — the
  state-based classifier makes the reason-string whitelist redundant.
- Delete streamEndReason() and its handleClose call — it existed only
  to feed the deleted classifier; upstream close paths still stamp
  specific reasons (CAS-once semantics preserved).
- Drop 3 now-unused imports from session_lifecycle.go.
- Update test fixtures: abnormalOnCloseFor(false) and
  TestConsecutiveFailures_UserReasonNotAbnormal stamp prevStateAtClose
  = WSC before onClose to match real session.Close() → handleClose
  state history.

Net -49 LOC; all internal/transport tests pass under -race.
Restores the sessionz closesByReason observability that the previous
state-based-classification refactor dropped. Transport-level closes
(readLoop.Recv returning a gRPC status error with no prior close-reason
stamp) previously showed as "Unspecified" in sessionz's histogram; they
now show as "StreamEnd:Unavailable", "StreamEnd:Canceled", etc. so
operators can distinguish transport-drop causes.

Kept minimal — one inline stamp guarded by err != nil. setCloseReason
is CAS-once, so upstream stampers (GoAway, MissedHeartbeat, Error, User,
etc.) still win when they ran first.

Also strips Java-parity language from three doc comments touched by
the recent state-based-classification refactor (per the Go docstring
rule — describe Go behavior, not the reference implementation).

Files:
- session_lifecycle.go: +1 stamp line, +1 status import, -Java ref in
  handleSessionResponse docstring
- session.go: -Java ref from prevStateAtClose docstring
- session_pool_lifecycle.go: -2 Java refs from noteAbnormalCloseIfAny
  docblock + inline comment
status.Code(io.EOF) returns codes.Unknown, so the default arm of
handleClose's close-reason stamper renders a graceful server-side
shutdown as "StreamEnd:Unknown" — hiding a distinct, common signal
in sessionz's "Unspecified" bucket.

Special-case errors.Is(err, io.EOF) → "StreamEnd:EOF" before falling
through to the default arm. Ctx errors (Canceled, DeadlineExceeded)
are already mapped by grpc-go's status helpers, so the default arm
still covers them correctly.
@sushanb
sushanb requested review from a team as code owners July 28, 2026 20:12
@product-auto-label product-auto-label Bot added the api: bigtable Issues related to the Bigtable API. label Jul 28, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the session closing classification from a reason-string-based mechanism to a state-history-based mechanism. It introduces prevStateAtClose in the Session struct to capture the state immediately before transitioning to StateClosed, allowing the session pool to accurately identify abnormal closes. The helper functions streamEndReason and isAbnormalCloseReason have been removed, and tests have been updated accordingly. There are no review comments, so I have no additional feedback to provide.

@sushanb
sushanb merged commit f2905b7 into googleapis:main Jul 28, 2026
19 checks passed
sushanb pushed a commit that referenced this pull request Aug 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.52.0](bigtable/v1.51.0...bigtable/v1.52.0)
(2026-08-03)


### Features

* **bigtable:** Add AFE picker (Simple / LeastInFlight / LeastLatency)
([#20204](#20204))
([bcbf714](bcbf714))
* **bigtable:** Add ClientConfig.DisableSession to opt out of session
backend
([#20297](#20297))
([7ee5e44](7ee5e44))
* **bigtable:** Add getClientConfigDirectAccessChecker for session pools
([#20209](#20209))
([3b8d30a](3b8d30a))
* **bigtable:** Add NoOpChannelPrimer for session channel pools
([#20208](#20208))
([d055a8a](d055a8a))
* **bigtable:** Add per-AFE sessionList for the two-tier session pool
([#20224](#20224))
([dbf0c3f](dbf0c3f))
* **bigtable:** Add protoRowToRow conversion helper for TableShim
([#20257](#20257))
([1297143](1297143))
* **bigtable:** Add Session debug surface (observability fields +
methods)
([#20211](#20211))
([d8d3e16](d8d3e16))
* **bigtable:** Add Session lifecycle (Start, Close, ForceClose,
readLoop, heartBeatLoop)
([#20215](#20215))
([b9e53c6](b9e53c6))
* **bigtable:** Add Session struct + state machine
([#20117](#20117))
([09acbb3](09acbb3))
* **bigtable:** Add session.Config.EnableDebug to gate sessionz debug
state
([#20247](#20247))
([ce74c31](ce74c31))
* **bigtable:** Add SessionClient + SessionTable + lazyPool
([#20228](#20228))
([ab2c96c](ab2c96c))
* **bigtable:** Add SessionPoolImpl (two-tier pool + scaling + debug)
([#20225](#20225))
([683eda8](683eda8))
* **bigtable:** Rename session pool display to
&lt;resource-id&gt;-&lt;PERM&gt;
([#20248](#20248))
([35e146e](35e146e))
* **bigtable:** Route Client.Open()-returned *Table through the Diverter
([#20273](#20273))
([2b81c7d](2b81c7d))
* **bigtable:** State-based classification for abnormal session close
([#20243](#20243))
([f2905b7](f2905b7))
* **bigtable:** TableShim fallback to classic on session UNIMPLEMENTED
([#20269](#20269))
([36540af](36540af))
* **bigtable:** TTL-on-idle cache for per-resource session.TableAPI
([#20263](#20263))
([00b2a49](00b2a49))
* **bigtable:** Wire Diverter on Client and route Open* via TableShim
([#20256](#20256))
([b32fbd7](b32fbd7))


### Bug Fixes

* **bigtable:** AFE picker latency signal — subtract poolWait and
compute TransportLatency = wire − backend at source
([#20281](#20281))
([bb8c4d5](bb8c4d5))
* **bigtable:** Guard NewStream OnFinish against grpc-go double-fire
([#20295](#20295))
([b51da29](b51da29))
* **bigtable:** Real per-resource pool teardown on sessionTable.Close +
cache close-race gate
([#20264](#20264))
([599aea9](599aea9))
* **bigtable:** Session.durations / session.uptime — set explicit
histogram bucket boundaries
([#20276](#20276))
([97eee22](97eee22))
* **bigtable:** SessionTableHandle self-heals across cache eviction
([#20296](#20296))
([0dd98cd](0dd98cd))
* **bigtable:** Translate ctx errors to gRPC status on session vRPC
([#20299](#20299))
([0f3b2a5](0f3b2a5))
* **bigtable:** Treat PingAndWarm NotFound as a successful prime
([#20219](#20219))
([a1557ad](a1557ad))


### Performance Improvements

* **bigtable:** Delete periodic Tick loop; sizing is event-driven
([#20285](#20285))
([2c096bd](2c096bd))
* **bigtable:** Drop pick_lost_race debug tag from CheckoutSession hot
path
([#20280](#20280))
([bd0e400](bd0e400))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigtable Issues related to the Bigtable API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants