Skip to content

fix(bigtable): session client — single ownership + single-flight pool loading - #20368

Merged
mutianf merged 2 commits into
googleapis:mainfrom
mutianf:refactor-bigtable-session-loading-cache
Aug 13, 2026
Merged

mutianf merged 2 commits into
googleapis:mainfrom
mutianf:refactor-bigtable-session-loading-cache

Conversation

@mutianf

@mutianf mutianf commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Approach 2 of 2 — Java-shape single ownership + single-flight loading

Second of two alternative fixes for the cold-start burst bug where a session pool is Close()d and never reopens, leaving every op for the run returning ErrPoolClosed. (Approach 1, the localized opened()-gate + refcount patch, is #20366.)

The bug

A pool was keyed only by (resource, permission), so every sessionTable for the same resource shared one entry in sessionPools. Two independent races then closed a pool a live sibling still referenced:

  • Burst / loser-discard: TableCache.GetOrOpen runs openFn for all concurrent missers and discards the losers via api.Close(). The loser's release closed the shared pool the winner had already memoized in its lazyPool.
  • Evict-then-reopen: a TTL-evicted handle's self-heal resolves a fresh sessionTable that picks up the still-mapped pool during the delete-then-close window; the old handle's release then closes it out from under the new one.

Either way lazyPool memoizes the closed Invoker forever and there is no path to reopen → ErrPoolClosed for the rest of the run.

The fix

Mirror the java-bigtable shape faithfully: the cache value solely owns its pool, and loads are single-flight. Two structural changes replace the shared-key registry.

1. Single ownership. Each buildLazyOpener invocation mints its OWN pool (no keyed dedup) and hands the pool's teardown to a per-owner poolCloser. sessionTable.closeRead/closeWrite are that owner's single-shot Close, so a table only ever closes the pool ITS opener created — by captured identity — never a sibling sessionTable's pool that happens to share a (resource, permission).

  • getOrCreateSessionPool/releaseSessionPool/buildLazyReleaser are gone; createSessionPool always creates and returns an id-scoped releaser, and releasePoolByID tears down by id.
  • sessionPools becomes an id-keyed observational index (sessionz snapshots, config-listener fanout, Client.Close close-all) rather than the ownership/dedup key. poolKey loses its nonce and survives only to label the OTel session_name metric and order snapshots.
  • poolCloser also handles the Close-straddles-open race: if the table is torn down before the opener finishes wiring, set() closes the fresh pool on the spot instead of leaking it.

2. Single-flight loading. TableCache.GetOrOpen is now single-flight per key via a loading map + ready channel, matching Guava LoadingCache's atomic single-load contract. A post-eviction RPC storm collapses onto one openFn call and installs exactly one successor pool instead of dialing N and discarding N-1. The closed-gate still releases a handle whose open straddled cache.Close (the interleaving that previously deadlocked a naive single-flight is handled by keeping the loadState separate from the always-complete TableHandle).

Notes

  • Supersedes the earlier nonce bolt-on, which kept the shared-key registry and left a dead dedup lookup — @mutianf flagged that it wasn't the Java shape originally proposed.
  • Test changes are in scope for this refactor: releaseSessionPool tests → id-based releasePoolByID tests; buildLazyOpener test gains the owner arg; the eviction-storm test's assertions are unchanged (they already tolerated single-flight) but its stale "NOT single-flight" / "loser-close" comments are corrected.
  • newSessionTable / lazyPool signatures are unchanged. Full session suite passes under -race.

@mutianf
mutianf requested review from a team as code owners August 12, 2026 02:00
@product-auto-label product-auto-label Bot added the api: bigtable Issues related to the Bigtable API. label Aug 12, 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 nonce field to poolKey to ensure each Open* call owns a private pair of pools, preventing a shared-pool race. The sessionClient now uses an atomic counter to generate unique nonces for tables, authorized views, and materialized views. Feedback suggests adding a test case with a non-zero nonce to verify that displayName ignores the nonce field, preventing potential OTel metric cardinality issues.

Comment thread bigtable/internal/session/client_test.go
… loading

Rebuild the session-pool ownership model to mirror Java's shape so a
cold-start burst can no longer close a pool that a live sibling still
uses (the ErrPoolClosed-forever bug).

Two structural changes replace the previous shared-key registry:

  * Single ownership. Each buildLazyOpener invocation mints its OWN
    pool (no keyed dedup) and hands the pool's teardown to a per-owner
    poolCloser. sessionTable.closeRead/closeWrite are that owner's
    single-shot Close, so a table only ever closes the pool ITS opener
    created — by captured identity — never a sibling sessionTable's pool
    that happens to share a (resource, permission). sessionPools becomes
    an id-keyed observational index (sessionz snapshots, config-listener
    fanout, Close-all) rather than the ownership/dedup key. poolCloser
    also handles the Close-straddles-open race: if the table is torn
    down before the opener finishes wiring, set() closes the fresh pool
    on the spot instead of leaking it.

  * Single-flight loading. TableCache.GetOrOpen is now single-flight per
    key via a loading map + ready channel, matching Guava LoadingCache's
    atomic single-load contract. A post-eviction RPC storm collapses
    onto one openFn call and installs exactly one successor pool instead
    of dialing N and discarding N-1. The closed-gate still releases a
    handle whose open straddled cache.Close.

This supersedes the earlier nonce bolt-on, which kept the shared-key
registry and left a dead dedup lookup.

Change-Id: I3e664ab411e4b4d468a7ef44e117fe76d0f28112
@mutianf
mutianf force-pushed the refactor-bigtable-session-loading-cache branch from 44a6820 to e4dc647 Compare August 12, 2026 15:30
@mutianf mutianf changed the title fix(bigtable): session client — give each Open* a privately owned pool fix(bigtable): session client — single ownership + single-flight pool loading Aug 12, 2026
@mutianf mutianf closed this Aug 12, 2026
@mutianf
mutianf deleted the refactor-bigtable-session-loading-cache branch August 12, 2026 15:49
@mutianf
mutianf restored the refactor-bigtable-session-loading-cache branch August 12, 2026 20:26
@mutianf mutianf reopened this Aug 12, 2026
Comment thread bigtable/internal/session/table_cache.go
Comment thread bigtable/internal/session/client.go Outdated
Harden the single-flight loader and pool-teardown paths per PR review:

- TableCache.GetOrOpen: clear the loading slot and wake waiters in a
  defer so a panic deep in openFn (buildLazyOpener →
  createSessionPoolForPayload → pool construction) can't leave the slot
  wedged forever. Previously the inline cleanup after openFn was
  unreachable on panic, deadlocking every subsequent GetOrOpen(key) and
  wedging the resource until restart. Add a regression test.

- buildLazyOpener: capture and join the teardown error from
  owner.set(release) instead of discarding it, so a failed drain when the
  sessionTable was closed mid-dial surfaces rather than being swallowed.

- Add a single-ownership test pinning that two Open* calls resolving the
  same poolKey get distinct pools and independent id-scoped closers.

- Drop stale "Java parity" phrasing from an idempotency test comment.

Change-Id: Ie7f8b144dbb1a12f9393025b6e6d51f391cc07a0
@mutianf
mutianf enabled auto-merge (squash) August 13, 2026 15:32
@mutianf mutianf added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 13, 2026
@kokoro-team kokoro-team removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 13, 2026
@mutianf
mutianf merged commit f4ad950 into googleapis:main Aug 13, 2026
19 checks passed
@mutianf
mutianf deleted the refactor-bigtable-session-loading-cache branch August 13, 2026 16:41
sushanb pushed a commit that referenced this pull request Aug 28, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.53.0](bigtable/v1.52.0...bigtable/v1.53.0)
(2026-08-27)


### Features

* **bigtable/accelerator:** Add --user-agent flag to daemon
([#20341](#20341))
([6b33c06](6b33c06))
* **bigtable/accelerator:** Add daemon binary with identity verification
([#20330](#20330))
([2f9cf89](2f9cf89))
* **bigtable/accelerator:** Add proxy channel scaffold owning a sessi…
([#20310](#20310))
([d65ca7d](d65ca7d))
* **bigtable/accelerator:** Add stdin-EOF and parent-PID lifecycle wa…
([#20328](#20328))
([dc1c6ae](dc1c6ae))
* **bigtable/accelerator:** Add UDS server with authenticated proxy i…
([#20320](#20320))
([a837020](a837020))
* **bigtable/accelerator:** Add V2-to-session request adapters
([#20271](#20271))
([4cb6f16](4cb6f16))
* **bigtable/accelerator:** Authenticate UDS callers with a stdin tok…
([#20324](#20324))
([7847ab7](7847ab7))
* **bigtable/accelerator:** Dispatch MutateRow and single-row ReadRow…
([#20312](#20312))
([c89f336](c89f336))
* Update API sources and regenerate
([#20369](#20369))
([bfe3aaf](bfe3aaf))
* Update sources and regenerate
([#20399](#20399))
([a6b5cdb](a6b5cdb))


### Bug Fixes

* **bigtable/internal/transport:** Drop redundant defer p.Close() in AFE
picker tests
([#20311](#20311))
([c05b767](c05b767))
* **bigtable:** Register c2p resolver + RLS balancer for accelerator
daemon DirectPath
([#20362](#20362))
([95f372f](95f372f))
* **bigtable:** Session client — set AllowNonDefaultServiceAccount on
DirectPath dial
([#20343](#20343))
([b9c76ec](b9c76ec))
* **bigtable:** Session client — single ownership + single-flight pool
loading
([#20368](#20368))
([f4ad950](f4ad950))
* **bigtable:** Session pool — re-drive creation after a never-activated
close so the consecutive-failure breaker is reachable
([#20355](#20355))
([ee74949](ee74949))
* **bigtable:** Use composed user agent as CSM client_name
([#20383](#20383))
([cb02335](cb02335))

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

3 participants