Skip to content

Threading refactor (1/7): Setup — VOperation, OpExecutor, hashed-wheel timer#1

Open
igorbernstein2 wants to merge 82 commits into
threading-refactor-basefrom
threading-refactor-phase-1
Open

Threading refactor (1/7): Setup — VOperation, OpExecutor, hashed-wheel timer#1
igorbernstein2 wants to merge 82 commits into
threading-refactor-basefrom
threading-refactor-phase-1

Conversation

@igorbernstein2

@igorbernstein2 igorbernstein2 commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 of 7 in the threading-refactor stack. Lays down infrastructure that the rest of the refactor will use — no behavior change.

This phase is purely additive: every commit either introduces new types or rewires plumbing without changing observable semantics.

What's in here

Commit What it does
chore: rationalize SessionImpl field visibility Adds @GuardedBy to fields under the lock; documents the few fields intentionally read outside it. Pure documentation pass — no runtime change. Sets up the next phase,
which removes the lock entirely.
chore: migrate pool scheduling to a hashed-wheel timer Replaces ScheduledExecutorService with BigtableTimer (Netty hashed wheel) for heartbeat, deadline, watchdog, AFE-prune, retry-create-session, and retry-delay.
Owned by Client and shared across pools.
chore: introduce VOperation as the head of the middleware chain Replaces CancellableVRpc with a VOperation layer that sits above the VRpc chain rather than inside it. VOperationImpl owns the gRPC Context
cancellation listener and constructs the per-op VRpcCallContext.
chore: introduce OpExecutor; plumb VRpcCallContext.getExecutor VRpcCallContext.getExecutor() returns OpExecutor (thin wrapper with runningThread affinity tracking). VOperationImpl constructs the per-call
SynchronizationContext + OpExecutor; RetryingVRpc drops its own SyncContext and dispatches via ctx.getExecutor(). The uncaught-handler safety net moves from RetryingVRpc up to VOperationImpl.
chore: move callback dispatch from RetryingVRpc to VRpcImpl VRpcImpl.handle*() methods now dispatch listener callbacks via ctx.getExecutor(), with CAS STARTED→CLOSED in all three. start() publishes
ctx/listener only after winning the CAS so a racing duplicate can't corrupt the winner's fields.

Stack position

  • Base: threading-refactor-base (= origin/main)
  • Next: threading-refactor-phase-2 — SessionImpl: synchronized(lock)SynchronizationContext

whowes and others added 10 commits June 12, 2026 16:07
…s#13338)

This should improve efficiency/reliability by avoiding OAuth token
exchange - see https://google.aip.dev/auth/4111.

BigQueryOptions (b/523372553) and StorageOptions (b/523372960) are
disabled by default for now.
🤖 I have created a release *beep* *boop*
---


<details><summary>1.88.0-SNAPSHOT</summary>

### Updating meta-information for bleeding-edge SNAPSHOT release.
</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>
…#13455)

Reuse the CharsetEncoder in ChecksumResultSet to prevent the creation of
a new encoder for each string that we encounter.
)

1. Ensure a minimum capacity of 4 bytes when allocating the buffer, this
is the max size of a UTF-8 character. However, the java length
representation is being used in this code for the byte buffer
allocation, which may be too small for a single utf-8 character.
  
2. Use buffer.clear() instead of the second buffer.flip() . This resets
the buffer's write limit back to its full capacity for the next
iteration, instead of shrinking it to the size of the previous write.
This is for mixed multi-byte utf-8 characters and single-byte
characters. A test was added to show this passing, and it fails with
`flip()` vs `clear()`


Fixes googleapis#13440
Put in manual fix in librarian.yaml for released_version for firestore,
biglake and errorreporting.
This is a temporarily fix to unblock development in google-cloud-java,
track remaining work in
googleapis/librarian#6419.

Fixes googleapis#13447
)

Followup to googleapis#13351,
add backstory back to librarian.yaml and re-generate code with
librarian.

Manual add config to librarian.yaml and run:
```
V=$(go run github.com/googleapis/librarian/cmd/librarian@latest config get version)
go run github.com/googleapis/librarian/cmd/librarian@${V} install
export PATH=$PATH:$HOME/.cache/librarian/bin/java_tools/bin
go run github.com/googleapis/librarian/cmd/librarian@${V} generate backstory
```
Then manually reverted version change in
[gapic-libraries-bom/pom.xml](googleapis@4852572),
this is known limitation during partial release. See
googleapis/librarian#6411.

Fixes googleapis/librarian#6409
This PR
- Introduces awaitility version 4.3.0 to gapic-generator-java-pom-parent
and gax-java.
- Migrate one test ThresholdBatcherTest to use it as an example.

This is the first PR of b/505479343. Follow up PRs would migrate all
tests in sdk-platform-java to use awaitility.
@igorbernstein2 igorbernstein2 changed the title Threading refactor phase 1 - Setup Threading refactor (1/7): Setup — VOperation, OpExecutor, hashed-wheel timer Jun 16, 2026
gyang-google and others added 6 commits June 16, 2026 15:10
googleapis#13239)

Adding CumulativeHasher wrapper class for Full object checksum

Refer to: go/full_checksum_java

---------

Co-authored-by: Dhriti Chopra <dhritichopra@google.com>
…eapis#13472)

This commit adds explicit assertions to verify that the generated JWS
header correctly contains 'alg=RS256' and 'typ=JWT', and that the JWT
payload contains the 'iat' and 'exp' claims with exactly a 3600-second
(1-hour) expiration offset.

This brings the Java library's test suite into alignment with the
expected auth specification. Other Google Cloud client libraries like
Go, Node.js, and Python natively assert the presence of these standard
headers and the 1-hour expiration window during their Self-Signed JWT
generation tests.
…oogleapis#13474)

This commit adds a test to verify that when a Service Account is
configured with multiple scopes during Self-Signed JWT authentication,
the scopes are correctly serialized into a single space-separated string
within the JWT's 'scope' claim.

This brings the Java library's test suite into alignment with the
expected auth specification. Other Google Cloud client libraries like
Go, Node.js, and Python natively assert that multiple scopes are
appropriately space-separated during their JWT generation tests.
…googleapis#13459)

In DefaultCredentialsProviderTest.java, the
getDefaultCredentials_quota_project test case was missing the JUnit
@test annotation, causing it to be ignored. This adds the annotation to
ensure it is executed during test runs.
…eapis#13465)

This PR fixes a critical security issue where the plaintext clientSecret
of UserCredentials was being leaked and written to disk under the key
quota_project, instead of the actual quotaProjectId under
quota_project_id.
private final boolean idempotent;
private final Context.CancellationListener cancellationListener;

public VOperationImpl(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why not create VRpcContext and pass VRpcContext in the OperationImpl?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Not sure I understand. The intent of VOperation is to be the VRpcContext factory. Can you clarify what you mean?

* SynchronizationContext#throwIfNotInThisSynchronizationContext}).
*
* <p>Backing executor evolves over the refactor — for now it is the per-call {@link
* io.grpc.SynchronizationContext} that {@link VOperationImpl} constructs. Later commits swap it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Are we gonna eventually remove this wrapper? The serialization guarantee still comes from the backing executor, and the OpExecutor only makes it easier for refactoring. I don't think it brings much extra value? And I was very confused looking at this code so I wonder if leaving this around would be confusing..

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

which wrapper? OpExecutorImpl will be the replacement for guava's SequentialExecutor

@Override
public void start(ReqT req, VRpcCallContext ctx, VRpcListener<RespT> listener) {
if (!state.compareAndSet(State.NEW, State.STARTED)) {
// Lost the CAS — a duplicate start. Dispatch to the local listener/ctx without touching

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we update the comment to: "Lost the Compare-And-Swap (CAS)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Or maybe remove this comment, I think the code is pretty self explanatory 😂


void handleError(VRpcResult result) {
if (state.getAndSet(State.CLOSED) == State.CLOSED) {
// CAS STARTED -> CLOSED, matching handleResponse / handleSessionClose. The previous

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same here

Dhriti07 and others added 11 commits June 17, 2026 15:03
…gleapis#13265)

Adding full object checksum for grpc flow

Refer to: go/full_checksum_java

---------

Co-authored-by: Dhriti Chopra <dhritichopra@google.com>
…3471)

Other languages (such as Python and Node.js) handle missing HOME or
APPDATA directories by falling back gracefully or failing with a
structured Error/Exception. This change brings Java into parity by
ensuring an orderly IOException is thrown instead of an opaque
NullPointerException when APPDATA is null.

Fixes googleapis#12565
…ion in config (googleapis#13505)

Fix workflow to run librarian at version configured in librarian.yaml
consistently.

Fixes googleapis/librarian#6466
…gleapis#13501)

Weekly update of librarian version and re-generate. Use pseudo version
because v0.21.0 has a known issue for Java.
Command used:

```
go run github.com/googleapis/librarian/cmd/librarian@latest update version

# Get the updated version
V=$(go run github.com/googleapis/librarian/cmd/librarian@latest config get version)

# Regenerate everything to pick up librarian / sdk.yaml changes.
go run github.com/googleapis/librarian/cmd/librarian@${V} generate --all
```
b/511229401

This PR introduces the `rowsInPage` metadata to `TableResult` and
ensures it is populated for all pages (both the first page and
subsequent pages fetched via pagination).

Previously, metadata about the number of rows in the page was not easily
accessible or propagated across multiple paginated requests (e.g., when
calling `getNextPage()`). This change:
1. Adds a `rowsInPage` field and builder method to the `@AutoValue`
`TableResult` class.
2. Populates `rowsInPage` during initial page creation in `BigQueryImpl`
and empty results in `Job.java`.
3. Updates `TableResult.getNextPage()` to calculate the size of values
for subsequent pages via `Iterables.size` (which is $O(1)$ for
materialized lists) and sets it on the returned `TableResult` pages.
4. Updates unit test assertions in `TableResultTest.java` and
`SerializationTest.java` to verify that `rowsInPage` is populated and
propagated correctly.

## Key Changes
- **`TableResult.java`**: Added `getRowsInPage()`, builder method
`setRowsInPage()`, and updated `getNextPage()` to pass down the
calculated row count for the next page.
- **`BigQueryImpl.java`**: Set `rowsInPage` on initial query results
page creation.
- **`TableResultTest.java`**: Added assertions to verify that
`rowsInPage` is correctly populated for both the first page and
subsequent pages.

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Updated googleapis commitish in librarian.yaml and
generation_config.yaml to
googleapis/googleapis@7af3f2c

💡 **Note:** Please merge or close this PR so that the daily update
workflow can continue to run successfully the next day. Alternatively,
you can manually trigger the workflow once this PR is merged or closed.
…gleapis#13266)

Adding full object checksum for bidi flow

Refer to: go/full_checksum_java

---------

Co-authored-by: Dhriti Chopra <dhritichopra@google.com>
…pis#13429)

b/521443900

This PR introduces support for fetching a list of GCP projects via the
BigQuery API. This functionality is being exposed primarily to support
cross-project dataset resolution in the native BigQuery JDBC driver.

**Changes included:**
* **Domain Model:** Added `Project` domain model representing a BigQuery
project (`@BetaApi`).
* **Client Interface:** Added `listProjects(ProjectListOption...
options)` to the `BigQuery` client interface, marked as `@InternalApi`
to preserve the public GA surface.
* **SPI Layer:** Added `listProjects` mapping to `BigQueryRpc` and
implemented the underlying HTTP execution in `HttpBigQueryRpc`,
including pagination and OpenTelemetry tracing support.
* **Implementation:** Implemented `ProjectPageFetcher` inside
`BigQueryImpl` to seamlessly handle paginated project results.
* **Testing:** Added unit test coverage in `BigQueryImplTest` and
`HttpBigQueryRpcTest`.
…ts (googleapis#13484)

This PR migrates all remaining wait loops and polling blocks in GAX
tests to use Awaitility. This is the second part of b/505479343,
following the initial introduction of Awaitility in googleapis#13458.

Specifically, this migrates:
- Semaphore64Test: replaced mock thread wait loops with Awaitility state
waiting.
- ScheduledRetryingExecutorTest: migrated future attempt checking,
attempt cancellation wait, and first attempt latch count down to
Awaitility.
- BatcherImplTest: converted GC loops and thread state polling blocks to
Awaitility.
mutianf and others added 29 commits June 23, 2026 12:59
…googleapis#13531)

This adds BouncyCastle to the shared dependency BOM. It is required to
implement the OPAQUE PAKE protocol for Spanner Omni authentication
(which relies on Argon2id and low-level Elliptic Curve math not
available in standard Java/Tink).

Bug: b/526057728

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Add option to disable Bidi Checksum using checksum disable system
property

---------

Co-authored-by: Dhriti Chopra <dhritichopra@google.com>
Co-authored-by: Nidhi <nidhiii@google.com>
…googleapis#13517)

This PR updates the gapic-generator-java to add the JSpecify @NullMarked
annotation to all generated class declarations. Adding @NullMarked at
the class level defines the class scope as null-safe, establishing that
all unannotated types in method signatures (parameters and return types)
are nonnull able by default. This is the first phase in onboarding the
generated client libraries to compile-time safety validation, see design
doc for more details:
[go/sdk:java-jspecify-null-annotations-gapic](http://goto.google.com/sdk:java-jspecify-null-annotations-gapic)

Classes Annotated:
- Client Classes: AbstractServiceClientClassComposer
- Settings Classes: AbstractServiceSettingsClassComposer and
AbstractServiceStubSettingsClassComposer
- Stub Classes: AbstractServiceStubClassComposer and
AbstractTransportServiceStubClassComposer
- Callable Factories: AbstractServiceCallableFactoryClassComposer
- Resource Names: ResourceNameHelperClassComposer, CommonStrings

Implementation Changes:
- Added JSpecify import statements to class-level generation templates
- Registered NullMarked.class and Nullable.class in TypeStore across all
class composers to ensure references compile and resolve properly

Verification/Testing:
- Please refer to this doc:
https://docs.google.com/document/d/1h126pbTGqSwtJyl35E2_vQjM9gNtmWETYCM7PObcvmQ/edit?tab=t.0#heading=h.s4q06jxocx8b

Revisions:
- Mainly to fix the autogenerator, not related to logic: added goldens +
dependencies for the build

Next Steps:
- In the next PR, will add @nullable annotations
…ry (googleapis#13539)

b/526579065
googleapis#13494

This PR resolves an issue where the BigQuery JDBC driver fails to
connect/authenticate in proxy-enforced network environments, resulting
in authentication timeouts.

### Problem
While the driver successfully parses connection-specific proxy
parameters (`ProxyHost` and `ProxyPort` in the connection string) and
configures the main BigQuery client, it **does not** propagate them to
the Google Auth Library credential objects (used to fetch/refresh OAuth2
access tokens). As a result, token fetch requests bypass the proxy and
attempt direct egress to `oauth2.googleapis.com`, which is blocked by
the firewall.

### Solution
1. **Reordered Constructor:** Updated `BigQueryConnection.java` to parse
HTTP proxy settings and build `HttpTransportOptions` *before*
instantiating credentials.
2. **Propagated Transport Factory:** Extracted the proxy-configured
`HttpTransportFactory` and passed it into the credentials helper
(`BigQueryJdbcOAuthUtility.getCredentials(...)`).
3. **Updated Auth Utility:** Overloaded and updated all authentication
methods inside `BigQueryJdbcOAuthUtility.java`
(`getGoogleServiceAccountCredentials`, `getUserAuthorizer`,
`getExternalAccountAuthCredentials`, and
`getServiceAccountImpersonatedCredentials`) to accept
`HttpTransportFactory` and apply it to their respective credential
builders.

---

## Testing Done

### 1. Unit Tests
Added regression tests to `BigQueryJdbcOAuthUtilityTest.java` to assert
that `HttpTransportFactory` is correctly set on the built credentials:
* `testGetCredentialsPropagatesHttpTransportFactory` (Service Account
Credentials)
* `testGetImpersonatedCredentialsPropagatesHttpTransportFactory`
(Impersonated Credentials)

### 2. Manual Verification
Verified routing in a network-isolated environment using Docker:
* Set up a local **Squid proxy** on port `3128` and a mock HTTPS server
hosting the `/token` endpoint on port `45825` on the host loopback.
* Ran the client verifier container inside an isolated Docker bridge
network (blocking direct access to the host loopback).
* Configured the connection URL string with proxy details:
`;ProxyHost=host.docker.internal;ProxyPort=3128;`.
* **Result:** The connection was established successfully, and the Squid
proxy log recorded the authentication tunnel request:
  `CONNECT localhost:45825 - HIER_DIRECT/::1 -`
* Omitting proxy settings correctly threw a `Connection refused`
exception and left Squid logs empty, proving that proxy routing was
strictly enforced and active.
… for metadata methods (googleapis#13344)

b/499078725

This PR implements the `EnableProjectDiscovery` connection property into
the BigQuery JDBC driver. Enabling this property (default `false`)
allows JDBC database metadata methods (like `getCatalogs()` and
`getSchemas()`) to discover and query datasets across all Google Cloud
projects accessible to the client credentials, rather than being
confined to the single default `ProjectId` specified in the connection
URL.

#### Changes
- **Connection Parameter Parsing**: Added parsing for
`EnableProjectDiscovery` in `BigQueryJdbcUrlUtility` and configured it
in `DataSource`.
- **SDK Integration**: Implemented
`BigQueryConnection.getDiscoveredProjects()` to utilize the
`BigQuery.listProjects()` core SDK method
- **Resilience and Caching**:
- Connection-scoped caching is implemented via
`discoveredProjectsCache`.
- **Metadata Integration**: Integrated discovered projects inside
`BigQueryDatabaseMetaData.getAccessibleCatalogNames()` so that catalog
and schema metadata fetches automatically query across all accessible
projects when `EnableProjectDiscovery` is enabled.
… invalid JSON (googleapis#13473)

Other client libraries (such as Python, Go, and Rust) strictly validate
JSON syntax and reject malformed payload structures immediately. This
test ensures Java maintains parity by asserting that an IOException is
explicitly thrown when ADC JSON parsing fails, preventing silent
fallbacks.

This fills an untested gap in the Java ADC resolution suite.
…oogleapis#13427)

Fixes bug 475824752. Tracks the number of bytes received from the
transport layer and emits a warning log if GCS sends more bytes than
requested by the user during explicit range requests.

[Generated-by: AI]

---------

Co-authored-by: Dhriti07 <56169283+Dhriti07@users.noreply.github.com>
…t mode (googleapis#13503)

**Problem**

Manual commit mode does not work correctly because statement
cancellation triggers a transaction rollback.

**STR (Steps to Reproduce)**

1. Create a JDBC connection.
2. Disable auto-commit:

   ```java
   conn.setAutoCommit(false);
   ```
3. Execute a DML statement using a `PreparedStatement`.
4. Close the statement.
5. Call:

   ```java
   conn.commit();
   ```

**ER (Expected Result)**

* Closing a statement does not affect the active transaction.
* Pending changes remain available until an explicit `commit()` or
`rollback()` is performed on the connection.
* `conn.commit()` successfully persists transaction changes.

**AR (Actual Result)**

* Statement cleanup invokes `cancel()`.
* `cancel()` performs a transaction rollback.
* Pending transaction changes are discarded before `conn.commit()` is
called.
* `conn.commit()` has no effect because the transaction has already been
rolled back.

**Sample Java Code:**

```java
try (Connection conn = DriverManager.getConnection(jdbcUrl, props)) {
    conn.setAutoCommit(false);

    try (PreparedStatement ps =
             conn.prepareStatement(
                 "INSERT INTO test_table(id) VALUES (1)")) {
        ps.executeUpdate();
    } // statement cleanup invokes cancel() -> rollback

    conn.commit(); // nothing is committed
}
```

**Fix**

Remove transaction rollback from the statement cancellation path.
Transaction state is now managed exclusively through explicit
`Connection.commit()` and `Connection.rollback()` operations.

---------

Co-authored-by: Kirill Logachev <kirl@google.com>
…tion-scoped executor (googleapis#13556)

b/520400589

This PR optimizes thread management for JDBC metadata methods,
implementing a deadlock-free, bipartite thread pool design and unifying
resource lifecycle management.

### Key Changes

* **Bipartite Thread Pool Architecture (Deadlock Prevention)**:
* Redirected all outer, orchestrating fetcher tasks (parent tasks) to
the connection’s cached query executor
(`connection.getExecutorService()`). Because the cached pool scales
dynamically as needed, this structurally prevents pool-induced deadlocks
(thread starvation).
* Kept all inner, highly concurrent parallel network API calls (child
tasks) on the connection's fixed-size metadata executor
(`connection.getMetadataExecutor()`). This bounds parallel network
traffic to prevent overwhelming the BigQuery backend.
* *Result*: The driver is fully protected against pool-induced
deadlocks, even under high concurrency or if `metadataFetchThreadCount`
is configured to `1`.

* **Connection-Scoped Shared Executor**:
* Migrated all asynchronous metadata operations (`getSchemas`,
`getTables`, `getColumns`, `getProcedures`, `getProcedureColumns`,
`getFunctions`, and `getFunctionColumns`) from spawning raw threads or
short-lived local pools to connection-managed executors.
* Configured the shared metadata pool to be lazily instantiated on
demand and gracefully shut down with the connection lifecycle. Idle
threads are automatically reclaimed after 60 seconds.

* **Lower CPU Overhead (Sequential Row-Building)**:
* Completely removed secondary CPU-bound thread pools (such as
`routineProcessorExecutor`, `processArgsExecutor`,
`tableProcessorExecutor`, and `processParamsExecutor`) previously used
for row-building.
* Refactored row-building and parameter-processing tasks to execute
sequentially on the background fetcher threads, removing
context-switching overhead and preventing nested-pool deadlock risks.

* **Teardown of Legacy Bridges**:
* Deleted the obsolete `wrapThread(Thread)` compatibility adapter in
`BigQueryDatabaseMetaData.java`.
* Removed obsolete `testWrapThread_*` cases from
`BigQueryDatabaseMetaDataTest.java`. Metadata queries now track and
cancel background tasks natively via standard `Future<?>` handles.

* **Test Suite Refactoring**:
* Updated `BigQueryDatabaseMetaDataTest` setup to inject real,
connection-scoped cached executor services, ensuring that background
metadata tasks execute deterministically during testing.
* Added `@AfterEach` teardown logic to prevent test-suite thread leaks.
* Refactored argument-processing tests to verify row-building
synchronously without mock executors.
…t is disabled (googleapis#13543)

Fixes: googleapis#13469

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…13557)

This allows service clients to reuse that executor for other transport
related things in addition to gax
…oogleapis#13562)

Fixes location-aware routing for inline read-write transactions that
begin on the default endpoint.

When an inline read-write transaction (`TransactionSelector.BEGIN`)
starts on the default endpoint, the client must remember that default
endpoint affinity for the returned transaction ID. Without this, later
statements in the same transaction can be routed by CacheUpdate to a
direct endpoint, causing mid-transaction endpoint switches and ABORTED
failures.

This change records default endpoint affinity for inline read-write
begins, matching the existing behavior for explicit `BeginTransaction`
requests.
…ption (googleapis#13493)

Update composeObject snippet to support deleteSourceObjects option.
Add corresponding system integration tests.

[Generated-by: AI]

Co-authored-by: Dhriti07 <56169283+Dhriti07@users.noreply.github.com>
Document lock ownership before later steps remove the lock. Add @GuardedBy
to fields under the lock, move heartbeatInterval read into the synchronized
block in startRpc(), and comment fields intentionally read outside the lock.
No runtime change.
Replace ScheduledExecutorService with a BigtableTimer (Netty hashed wheel,
will move in-tree later) for heartbeat, deadline, watchdog, AFE-prune,
retry-create-session, and retry-delay. Owned by Client and shared across
pools.
Replaces CancellableVRpc with a VOperation layer that sits above the VRpc
chain rather than inside it. VOperationImpl owns the gRPC Context
cancellation listener and constructs the per-op VRpcCallContext; downstream
middleware just sees the chain.
VRpcCallContext.getExecutor() returns OpExecutor (thin wrapper with
runningThread affinity tracking). VOperationImpl constructs the per-call
SynchronizationContext + OpExecutor; RetryingVRpc drops its own SyncContext
and dispatches via ctx.getExecutor(). The uncaught-handler safety net moves
from RetryingVRpc up to VOperationImpl.
VRpcImpl.handle*() methods now dispatch listener callbacks via
ctx.getExecutor(), with CAS STARTED->CLOSED in all three (handleError no
longer proceeds from NEW) and decode moved into the executor task.
RetryingVRpc.Active drops its own wrap since callbacks already arrive on
the op executor. start() publishes ctx/listener only after winning the CAS
so a racing duplicate can't corrupt the winner's fields. SessionPoolImpl's
three direct listener.onClose paths also dispatch via ctx.getExecutor().
The pool-wide heartbeat monitor on main only inspected sessions in
inUseSessions (i.e. with an active vRPC). When heartbeat scheduling
moved into SessionImpl, the per-session timer was instead armed once on
ready and self-rescheduled indefinitely. The "no active vRPC" case
was reduced to a sentinel (nextHeartbeat = now + FUTURE_TIME), which
made checkHeartbeat no-op but kept the wheel ticking. It also left a
latent force-close on idle sessions: handleHeartBeatResponse
unconditionally resets nextHeartbeat to now + heartbeatInterval, so an
idle session that received a server heartbeat became eligible for
force-close on the next missed beat.

Tie the timer to the vRPC lifecycle to match main's semantic:

- handleOpenSessionResponse no longer schedules the tick.
- startRpc arms the tick after setting nextHeartbeat.
- handleVRpcResponse / handleVRpcErrorResponse cancel the tick.
- updateState (already cancels on transition past READY).

Add SessionImplTest coverage with a counting BigtableTimer wrapper that
asserts (a) no schedule before any vRPC and (b) exactly one
schedule/cancel per vRPC lifecycle.
@igorbernstein2
igorbernstein2 force-pushed the threading-refactor-phase-1 branch from baf7ac7 to be630ac Compare June 29, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.