Repository navigation
fix(bigtable): replace SessionPoolImpl Watchdog with per-session closeTimeout - #14977
Conversation
β¦ining SessionPoolImpl.close() previously left the watchdog running until awaitTerminated() was called. Callers that close a pool without calling awaitTerminated() (such as TableBase.close() when an idle table is evicted from compat.Util.createSessionMap) leaked the self-rescheduling watchdog on the client's shared HashedWheelTimer, retaining the closed SessionPoolImpl in memory. Close the watchdog as soon as all sessions drain after close() (immediately if the pool is empty, or in onSessionClose when the last session terminates), and guard onSessionGoAway against creating replacement sessions after the pool is closed.
There was a problem hiding this comment.
Code Review
This pull request ensures that the watchdog timer is closed when the session pool is closed and all sessions have drained, preventing potential timer leaks when callers only invoke close() without calling awaitTerminated(). A new test is added to verify this behavior. The review feedback suggests using System.nanoTime() instead of System.currentTimeMillis() in the test's wait loop to ensure monotonic time measurements and avoid sensitivity to system clock adjustments.
| long deadlineMs = System.currentTimeMillis() + 10_000; | ||
| while (!sessionPool.watchdog.isClosed() && System.currentTimeMillis() < deadlineMs) { | ||
| Thread.sleep(10); | ||
| } |
There was a problem hiding this comment.
Using System.currentTimeMillis() to measure deadlines or elapsed time is sensitive to system clock adjustments (such as NTP updates). It is safer and more robust to use System.nanoTime() for monotonic time measurements in test wait loops.
| long deadlineMs = System.currentTimeMillis() + 10_000; | |
| while (!sessionPool.watchdog.isClosed() && System.currentTimeMillis() < deadlineMs) { | |
| Thread.sleep(10); | |
| } | |
| long deadlineNano = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); | |
| while (!sessionPool.watchdog.isClosed() && System.nanoTime() < deadlineNano) { | |
| Thread.sleep(10); | |
| } |
Remove SessionPoolImpl.Watchdog and instead arm a one-shot closeTimeout (CLOSE_SESSION_TIMEOUT = 5 minutes) on SessionImpl when entering WAIT_SERVER_CLOSE in startGracefulClose(), cancelled in updateState() when leaving WAIT_SERVER_CLOSE. This eliminates the recurring per-pool watchdog timer task on Client.sessionTimer and matches how openTimeout and heartbeatTimeout are managed per session.
Remove unused Session.getLastStateChange(), add a unit test for onSessionGoAway on a closed pool using monotonic System.nanoTime(), pass FakeClock in sessionCloseTimeoutForcesClose, and update stale watchdog comments in Client and SessionPoolImplTest.
β¦onReason import
π€ I have created a release *beep* *boop* --- <details><summary>1.97.0</summary> ## [1.97.0](v1.96.0...v1.97.0) (2026-11-12) ### β BREAKING CHANGES * **shopping-merchant:** remove discontinued Merchant API v1beta ([#14942](#14942)) ### Features * **bigquery-jdbc:** adds BigQuery graph integration to DatabaseMetadata methods ([#14930](#14930)) ([950b81b](950b81b)) * **gax:** implement resumable upload session resumption ([#14677](#14677)) ([24728d6](24728d6)) * **grpc-gcp:** add built-in channel_count metric for client library pools ([#14947](#14947)) ([712f043](712f043)) * **grpc-gcp:** add built-in channel_load metric for client library pools ([#14985](#14985)) ([9f87888](9f87888)) * **shopping-merchant:** remove discontinued Merchant API v1beta ([#14942](#14942)) ([2947dfd](2947dfd)) * **storage:** add StorageErrorTypeMapper for standard metrics ([#14694](#14694)) ([a9ac393](a9ac393)) * **storage:** add StorageMetricsContext for operation metrics state ([#14728](#14728)) ([edea395](edea395)) * **storage:** wire logical operation metrics in Retrying and StorageOptions ([#14695](#14695)) ([589c66c](589c66c)) * update API sources and regenerate ([#14958](#14958)) ([75944f3](75944f3)) ### Bug Fixes * **auth:** verify public/private key match with retries in mTLS keystore loading ([#14829](#14829)) ([096dbd6](096dbd6)) * **bigquery:** set QueryResultsFormat.ARROW in QueryArrow sample ([#14960](#14960)) ([c98e7c3](c98e7c3)) * **bigtable:** avoid triggering per-RPC credential session fallback on default call context ([#14961](#14961)) ([eb06d2c](eb06d2c)) * **bigtable:** copy Metadata per GetClientConfiguration call ([#14950](#14950)) ([278fe2f](278fe2f)) * **bigtable:** disable SwitchingChannelPool when ChannelPoolConfiguration is empty ([#14911](#14911)) ([fa24702](fa24702)) * **bigtable:** handle empty SessionPoolConfiguration in SessionPoolImpl and PoolSizer ([#14912](#14912)) ([cc7b235](cc7b235)) * **bigtable:** replace SessionPoolImpl Watchdog with per-session closeTimeout ([#14977](#14977)) ([a15acfb](a15acfb)) * **spanner-jdbc:** report database default isolation level ([#14929](#14929)) ([7e7595a](7e7595a)) * **spanner:** fix PG SHOW isolation level statements ([#14904](#14904)) ([bc917ed](bc917ed)) ### Dependencies * bump grpc to 1.85.1, opentelemetry to 1.67.0, and guava to 33.7.2-jre ([#14957](#14957)) ([1a27a00](1a27a00)) * bump guava to 33.7.1-jre and jspecify to 1.0.1 ([#14937](#14937)) ([1441527](1441527)) ### Documentation * **spanner:** update CHANGELOG.md for java spanner releases ([#14935](#14935)) ([956681e](956681e)) * **storage:** add Rapid Cache samples ([#14886](#14886)) ([e714f62](e714f62)) </details> --- 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>
SessionPoolImplpreviously ran a recurring pool-levelWatchdogonClient.sessionTimerthat was only stopped insideawaitTerminated(), so callers that closed a pool without callingawaitTerminated()(such asTableBase.close()on cache eviction incompat.Util.createSessionMap) leaked the self-rescheduling watchdog task.Remove
SessionPoolImpl.Watchdogand instead arm a one-shotcloseTimeout(CLOSE_SESSION_TIMEOUT = 5 minutes) onSessionImplwhen enteringWAIT_SERVER_CLOSEinstartGracefulClose(), cancelled inupdateState()as soon as the session leavesWAIT_SERVER_CLOSE(matching howopenTimeoutandheartbeatTimeoutare managed per session). Also guardonSessionGoAway()against creating replacement sessions after the pool is closed.