Threading refactor (1/7): Setup — VOperation, OpExecutor, hashed-wheel timer#1
Threading refactor (1/7): Setup — VOperation, OpExecutor, hashed-wheel timer#1igorbernstein2 wants to merge 82 commits into
Conversation
…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
…13381) See https://google.aip.dev/auth/4111 - this should improve efficiency/reliability by avoiding OAuth token exchange Related googleapis#13338 will enable self-signed JWTs in [SpannerOptions](https://github.com/googleapis/google-cloud-java/blob/main/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java)
…LQueryMultiPage (googleapis#13417) b/467064659
) 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.
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( |
There was a problem hiding this comment.
why not create VRpcContext and pass VRpcContext in the OperationImpl?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Can we update the comment to: "Lost the Compare-And-Swap (CAS)"
There was a problem hiding this comment.
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 |
…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
…13504) Fixes: b/509979376
…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.
…apis#13510) Adds alternate headers for grafeas. Will wait to regenerate until googleapis/librarian#6481 is merged. For googleapis/librarian#6178
…r pure GAPIC libraries" (googleapis#13550) Reverts googleapis#13518
…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.
baf7ac7 to
be630ac
Compare
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
chore: rationalize SessionImpl field visibility@GuardedByto fields under the lock; documents the few fields intentionally read outside it. Pure documentation pass — no runtime change. Sets up the next phase,chore: migrate pool scheduling to a hashed-wheel timerScheduledExecutorServicewithBigtableTimer(Netty hashed wheel) for heartbeat, deadline, watchdog, AFE-prune, retry-create-session, and retry-delay.Clientand shared across pools.chore: introduce VOperation as the head of the middleware chainCancellableVRpcwith aVOperationlayer that sits above theVRpcchain rather than inside it.VOperationImplowns the gRPCContextVRpcCallContext.chore: introduce OpExecutor; plumb VRpcCallContext.getExecutorVRpcCallContext.getExecutor()returnsOpExecutor(thin wrapper withrunningThreadaffinity tracking).VOperationImplconstructs the per-callSynchronizationContext+OpExecutor;RetryingVRpcdrops its own SyncContext and dispatches viactx.getExecutor(). The uncaught-handler safety net moves fromRetryingVRpcup toVOperationImpl.chore: move callback dispatch from RetryingVRpc to VRpcImplVRpcImpl.handle*()methods now dispatch listener callbacks viactx.getExecutor(), with CASSTARTED→CLOSEDin all three.start()publishesStack position
threading-refactor-base(=origin/main)threading-refactor-phase-2— SessionImpl:synchronized(lock)→SynchronizationContext