Skip to content

feat(bigtable): add Session state enum - #19981

Merged
sushanb merged 2 commits into
googleapis:mainfrom
sushanb:session-states
Jun 23, 2026
Merged

feat(bigtable): add Session state enum#19981
sushanb merged 2 commits into
googleapis:mainfrom
sushanb:session-states

Conversation

@sushanb

@sushanb sushanb commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

First in a series of small PRs that together replace the all-at-once #19980. Introduces just the State enum used by the upcoming Session primitive, modeled on the SessionState enum in the Java Bigtable client so vocabulary and ordinals line up across language clients:

  • Six values: StateNew, StateStarting, StateReady, StateClosing, StateWaitServerClose, StateClosed
  • StateClosing is the client-initiated drain (before CloseSession is sent); StateWaitServerClose is the post-CloseSession wait for the server's EOF
  • A String() method for logs
  • An ordinal-pin test so log scrapers / metric labels stay stable across releases and match the Java phase values

Sessions move strictly forward through these values; once StateClosed is reached the session is terminal.

What's next (stacked on top)

  • Session struct + hooks
  • Session lifecycle (Start / Send / Close, read loop, heartbeat, frame handlers)
  • Invoke path (vRPC encode → send → await response → InvokeResult)
  • SessionTracer (OTel session-scoped metrics) lands last

Test plan

  • gofmt, goimports, go vet, staticcheck, revive clean
  • go test ./internal/transport/ -run TestState -count=1 passes (both TestState_String and TestState_OrdinalsPinned)

Introduce the State type used by the upcoming Session primitive: a small
five-value enum (New / Starting / Active / Closing / Closed) with a
String() method for logs and a test that pins the numeric ordinals so
log scrapers and metric labels stay stable across releases.

Sessions move strictly forward through these values; once StateClosed
is reached the session is terminal.

First in a series of small PRs that together replace the prior
all-at-once session-core PR (googleapis#19980).
@sushanb
sushanb requested review from a team as code owners June 15, 2026 20:45
@product-auto-label product-auto-label Bot added the api: bigtable Issues related to the Bigtable API. label Jun 15, 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 introduces a new State type to represent the lifecycle of a Session in the Bigtable transport layer, along with corresponding unit tests to verify string representation and ensure constant ordinals remain stable. The review feedback suggests renaming the package from internal to transport in both files to align with Go package naming conventions and match the directory structure.

Comment thread bigtable/internal/transport/session_state.go
Comment thread bigtable/internal/transport/session_state_test.go
// StateNew indicates the session is newly created and not yet active.
StateNew State = iota
// StateStarting indicates the session is dialing and handshaking.
StateStarting

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.

nit: maybe

Suggested change
StateStarting
StateReady

?

Also do we need "StateStarting" instead of just "Starting"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to StateReady in 755b90d.

On the State prefix — keeping it on every constant. Package-level Starting / Ready / Closed would collide with common identifiers and lose context at call sites (internal.StateReady reads better than internal.Ready). It's also the convention stringer would generate.

// StateClosing indicates the session is draining and shutting down. It
// covers both the pre-CloseSession drain and the post-CloseSession wait
// for the server's EOF.
StateClosing

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.

I think we need another state WAIT_SERVER_CLOSE? (mirrors the java implementation).

  • when client receives GoAway, we transition to Closing.
    • if there's still active vRPC, wait for those work to finish before notifying server that it can close this session
    • Otherwise notify the server and transition to WAIT_SERVER_CLOSE state.

And we can have a monitoring thread watching for sessions that's stuck in WAIT_SERVER_CLOSE and force close it.

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.

(go/cbt-jetstream-retry-client-impl for reference)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in 755b90d as StateWaitServerClose. Enum now mirrors Java's six states with matching ordinals (NEW / STARTING / READY / CLOSING / WAIT_SERVER_CLOSE / CLOSED = 0..5).

The transition behavior you described (drain outstanding vRPCs in StateClosing → send CloseSession → transition to StateWaitServerClose → wait for server EOF → terminal) is exactly what splitting the state into two enables. The actual transition code + the WAIT_SERVER_CLOSE watchdog land in the stacked Session lifecycle PR; this PR is just the enum so reviewers can see the vocabulary first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the link — using go/cbt-jetstream-retry-client-impl as the reference for the lifecycle PR.

Rename StateActive to StateReady and split StateClosing into a
client-initiated drain (StateClosing) and a post-CloseSession wait for
the server's EOF (StateWaitServerClose). Six states now, matching the
Java SessionState enum in google-cloud-java (NEW, STARTING, READY,
CLOSING, WAIT_SERVER_CLOSE, CLOSED) so the lifecycle vocabulary and
ordinals line up across language clients — telemetry and logs read the
same in both.

Tests pin the new ordinals (0..5) and string mappings.
@sushanb
sushanb requested a review from mutianf June 17, 2026 21:26
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jun 20, 2026
Replaces the previous one-shot 30s ForceClose goroutine from handleGoAway
with the WAIT_SERVER_CLOSE state machine the Java client uses (and that
mutianf asked for in googleapis#19981's SessionState review).

Lifecycle now matches the Java reference:

  New → Starting → Active → Closing → WaitServerClose → Closed

- Closing = we've decided to close, draining outstanding vRPCs before
  sending CloseSession.
- WaitServerClose = CloseSession sent; waiting for the server's EOF /
  trailers to confirm teardown.
- Closed = terminal.

Session.Close now drives the full sequence (drain → send CloseSession →
transition to WaitServerClose) and accepts being called from Closing too,
so handleGoAway can hand off to it instead of open-coding a teardown.
handleClose accepts the Closed transition from any non-terminal state
(including the new WaitServerClose), unchanged behavior in that regard.

handleGoAway spawns a goroutine that runs s.Close with a 30s ctx so the
drain + CloseSession dance happens off the readLoop. Reason "GoAway" is
stamped up-front via the existing CompareAndSwap-once setCloseReason so
the eventual handleClose's "StreamEnd" fallback can't overwrite it.

New SessionPoolImpl.sweepStuckSessions runs each PerformScaling tick
(1Hz) and force-closes any session parked in WaitServerClose for >30s.
This bounds the worst case when the server accepts CloseSession but
forgets to EOF the stream — without it the session would sit forever,
the pool would never retire it, and sessionsClosed / CloseReasons would
never bump (which is exactly what the prior sandbox-test snapshot was
showing).

UI: sessionz colors WaitServerClose the same as Closing (warning amber).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jun 20, 2026
…on sweep

Server-sent GoAway transitions the session to Closing and cancels in-flight
RPCs beyond lastAdmitted, but the protocol leaves the actual stream close
up to the server's eventual EOF. In practice we've observed GoAway'd
sessions sit in Closing forever — the pool keeps them in p.sessions,
OnClose never fires, and the pool can't replace them.

Adds the WAIT_SERVER_CLOSE state the Java client uses (and that mutianf
asked for in googleapis#19981's SessionState review) and drives the lifecycle to
completion deterministically:

New → Starting → Active → Closing → WaitServerClose → Closed

- Closing = we've decided to close, draining outstanding vRPCs before
  sending CloseSession.
- WaitServerClose = CloseSession sent; waiting for the server's EOF /
  trailers to confirm teardown.
- Closed = terminal.

Session.Close now drives the full sequence (drain → send CloseSession →
transition to WaitServerClose) and accepts being called from Closing too
so handleGoAway can hand off without open-coding the teardown.
handleClose's Closed transition is unchanged (already permitted from any
non-terminal state).

handleGoAway spawns a goroutine that runs s.Close with a 30s ctx so the
drain + CloseSession dance happens off the readLoop, then exits.

New SessionPoolImpl.sweepStuckSessions runs on every PerformScaling tick
(1Hz) and force-closes any session parked in WaitServerClose past
waitServerCloseGrace (30s). This bounds the worst case when the server
accepts CloseSession but forgets to EOF the stream.

TestState_String updated to cover the new state; TestClose_Graceful…
updated to assert WaitServerClose after Send instead of the old Closing.
@sushanb
sushanb merged commit 0748972 into googleapis:main Jun 23, 2026
18 checks passed
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 4, 2026
…am parity

Aligns the Session lifecycle enum value name and its String() output with
Java's SessionState.READY and with upstream PR googleapis#19981 (0748972). Our
fork used "Active" internally since before the naming settled; every
other client (Java, upstream Go) uses "Ready" so cross-language
telemetry and log-scraping tools can rely on a consistent vocabulary.

Mechanical rename across 10 files, 29 references:
- Constant StateActive → StateReady in session.go + call sites.
- String() output "Active" → "Ready" in session.go and updated
  matching test-table assertion in session_test.go.
- sessionz UI switch cases + state-chip display order.
- flightz + sessionz + session_snapshot test fixtures.
- Comments referring to "reached StateActive" / "to StateActive".

No behavioural change — this is naming only. sessionz JSON responses
now emit "state":"Ready" instead of "Active"; downstream scrapers need
to update if any.
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 9, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 9, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 9, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 9, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 9, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 10, 2026
Adds the Session struct itself along with the two "surface" halves that
have no lifecycle behavior of their own — the OTel tracer and the
in-process debug counters / event ring. Also adds SessionHandle, the
per-session slot the pool checkout / picker code holds. Lifecycle
(Start / Close / readLoop / heartBeatLoop) and vRPC (Invoke) come next
in the reshaped googleapis#20112.

- session.go — Session struct + hooks + NewSession + State
  transitions (transitionTo, is/notState, signalQuiescent), vrpcResult,
  sessionErr, unavailable, afeID. Uses the existing State enum from
  session_state.go (PR googleapis#19981) — no re-declaration.
- session_debug.go — embedded sessionDebug: counters (retries, okRpcs,
  errorRpcs, msgsSent, msgsRecv), per-session event ring, latency
  histogram, cluster-id map, WithSessionLogger / WithSessionPoolName
  options, RemoteAddr / SampleUptime / RecordTransportOverhead
  accessors. Consumes metrics.TransportTypeName (googleapis#20115) and
  recordDebugTag (googleapis#20114).
- session_tracer.go — OTel sessionTracer for per-session +
  per-attempt metrics (session duration, open latency, uptime,
  transport-overhead histogram). Consumes metrics.TransportTypeName.
- picker.go — SessionHandle wrapping *Session with the per-pick
  counters (Picks, Outstanding, LastActivity) the pool needs. Its
  concrete users (SessionPool, AFE picker) come in later PRs; the
  handle type is here because Session embeds an atomic.Pointer to it.

Part 3b/3c of the Session core sub-split. Stacks on
bigtable-session-primitives (Part 3a), googleapis#20115
(metrics.TransportTypeName export), and googleapis#20114 (debug tag counter).
hongalex pushed a commit that referenced this pull request Jul 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.51.0](bigtable/v1.50.0...bigtable/v1.51.0)
(2026-07-23)


### Features

* **bigtable:** Add ChainInterceptors and RetryingVRpc for vRPC pipeline
([#20185](#20185))
([c7a832a](c7a832a))
* **bigtable:** Add ClientConfigurationManager
([#19986](#19986))
([3a8f927](3a8f927))
* **bigtable:** Add debug tag counter (recordDebugTag / assertDebugTag)
([#20114](#20114))
([3c97590](3c97590))
* **bigtable:** Add lazyPool helper for on-demand session pool opening
([#20182](#20182))
([f6ae3fb](f6ae3fb))
* **bigtable:** Add PeakEwma continuous time-decay latency tracker
([#20187](#20187))
([9d124ef](9d124ef))
* **bigtable:** Add PoolSizer for server-driven session pool capacity
([#20189](#20189))
([57ebbeb](57ebbeb))
* **bigtable:** Add session package with SessionClient + SessionTableAPI
interfaces
([#20180](#20180))
([4b82fd2](4b82fd2))
* **bigtable:** Add Session primitives (AttemptOutcome, vRPC ctx,
msgtype)
([#20116](#20116))
([e1011e2](e1011e2))
* **bigtable:** Add Session state enum
([#19981](#19981))
([0748972](0748972))
* **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for
OpenSession pacing
([#20184](#20184))
([02e3c6d](02e3c6d))
* **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for
OpenSession pacing
([#20184](#20184))
([29be83e](29be83e))
* **bigtable:** Add sessionTracer for per-Session lifecycle + vRPC
metrics
([#20190](#20190))
([a466345](a466345))
* **bigtable:** Enable new auth library and JWT for instance admin
client
([#20013](#20013))
([21c4a44](21c4a44))
* **bigtable:** Modularize channel priming behind a ChannelPrimer
interface
([#20027](#20027))
([5214ab7](5214ab7))
* **bigtable:** Modularize Direct Access compatibility check
([#19987](#19987))
([a25e93d](a25e93d))
* **o11y:** Regenerate clients for LRO tracing
([#20107](#20107))
([779074e](779074e))


### Bug Fixes

* **bigtable:** Default cluster/zone in toOtelMetricAttrs to avoid
Monitoring reject
([#20178](#20178))
([14493f4](14493f4))
* **bigtable:** Eliminate stats-handler MD race in internal/metrics
tracer
([#20158](#20158))
([c387066](c387066))

---
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