Threading refactor (2/7): SessionImpl — synchronized(lock) → SynchronizationContext#2
Open
igorbernstein2 wants to merge 6 commits into
Open
Conversation
igorbernstein2
force-pushed
the
threading-refactor-phase-2
branch
from
June 17, 2026 20:02
a702c4e to
0f6a9e4
Compare
mutianf
approved these changes
Jun 18, 2026
nimf
approved these changes
Jun 20, 2026
| } | ||
|
|
||
| private void handleOpenSessionResponse(OpenSessionResponse openSession) { | ||
| sessionSyncContext.throwIfNotInThisSynchronizationContext(); |
There was a problem hiding this comment.
I guess it doesn't hurt to do it twice, but we just did it in the dispatchResponseMessage above.
Interestingly, for some branches in dispatchResponseMessage we add this duplication and for some don't, e.g., handleSessionParamsResponse, handleGoAwayResponse, handleSessionRefreshConfigResponse don't have it, while handleOpenSessionResponse, handleVRpcResponse, handleVRpcErrorResponse, and handleHeartBeatResponse do.
igorbernstein2
force-pushed
the
threading-refactor-phase-2
branch
from
June 22, 2026 21:41
0f6a9e4 to
932ad80
Compare
SessionImpl gains sessionSyncContext that serializes stream callbacks. onMessage/onClose dispatch onto it, and the per-session heartbeat tick trampolines through it. synchronized(lock) blocks remain inside the handlers — the two coexist for now. Affinity asserts added at boundary methods and every handle*.
SessionImpl.startRpc and cancelRpc now submit to sessionSyncContext rather than running synchronously on the caller. VRpcSessionApi.startRpc is void — errors flow through rpc.handleError() onto ctx.getExecutor(). VRpcImpl drops its synchronous post-startRpc error branch.
All session state mutations now run on sessionSyncContext, so the per-session Object lock is no longer needed. Public methods (start, close, forceClose, startRpc, cancelRpc) submit onto sessionSyncContext; nextRpcId becomes AtomicLong for the cross-thread newCall() caller. handleVRpcResponse and handleVRpcErrorResponse drop the localCancel/localRpc capture-and-recheck dance — sessionSyncContext serializes them now. Stale lock-era comments on the fields are replaced with a sessionSyncContext-ownership note. SessionImplTest.testHeartbeat polls for the now-async nextHeartbeat update.
Split terminal close into notifyTerminalClose (per-target try/catch fan-out) and abortFromUncaughtException (global handler). Uncaught syncContext exceptions always set closeReason to ERROR — the prior reason is folded into the description so tracer/metrics correctly attribute aborts. notifyTerminalClose synthesizes a fallback closeReason if missing: every caller sets it today (forceClose, startGracefulClose, dispatchStreamClosed, abortFromUncaughtException), but a future writer who forgets would NPE inside the fan-out — and the throw escapes to the syncContext uncaught handler, which early-returns on the already-CLOSED state and silently skips the remaining cleanup. The synthesizer mirrors startGracefulClose: log a warning with an IllegalStateException for stack-trace observability, then build a fallback CloseSessionRequest so the rest of the fan-out runs. Adds three regression tests (listener.onReady throws, onClose throws, both throw).
Five SessionImpl handlers reachable from dispatchResponseMessage — handleSessionParamsResponse, handleSessionRefreshConfigResponse, handleGoAwayResponse, handleUnknownResponseMessage — and the dispatchStreamClosed entry point were missing the throwIfNotInThisSynchronizationContext assertion that the other handlers already have. The assertions are strictly redundant today (every caller is either dispatchResponseMessage, which already asserts, or sessionSyncContext.execute), but the asymmetry is incidental and adding them uniformly documents the threading contract and guards against future direct callers.
igorbernstein2
force-pushed
the
threading-refactor-phase-2
branch
from
June 29, 2026 17:20
932ad80 to
f285a29
Compare
igorbernstein2
force-pushed
the
threading-refactor-phase-1
branch
from
June 29, 2026 17:20
baf7ac7 to
be630ac
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 2 of 7. Replaces the per-
SessionImplObject lockwith an explicitSynchronizationContext. Builds on the infrastructure introduced in [Phase 1].The migration is staged so each step keeps tests green:
startRpc/cancelRpc).What's in here
chore: add session SynchronizationContext alongside the existing locksessionSyncContextserializes stream callbacks.onMessage/onClosedispatch onto it; the per-session heartbeat tick trampolines through it.synchronized(lock)blocks remain inside the handlers — the two coexist for now. Affinity asserts added at boundary methods.chore: make startRpc and cancelRpc async via sessionSyncContextSessionImpl.startRpcandcancelRpcnow submit tosessionSyncContextrather than running synchronously on the caller.VRpcSessionApi.startRpcvoid— errors flow throughrpc.handleError()ontoctx.getExecutor().chore: remove synchronized(lock) from SessionImplsessionSyncContext, so the per-sessionObject lockis no longer needed.nextRpcIdbecomesAtomicLongfor thenewCall()caller.handleVRpcResponse/handleVRpcErrorResponsedrop thelocalCancel/localRpccapture-and-recheck dance —sessionSyncContextserializes them now.chore: abort session on uncaught exception in sessionSyncContext(+ closeReason synthesizer)notifyTerminalClose(per-target try/catch fan-out) andabortFromUncaughtException(globalERROR.notifyTerminalClosesynthesizes a fallback closeReason if missing — defensive against future writers who forget. Adds three regression tests.Stack position
threading-refactor-phase-1threading-refactor-phase-3— executor isolation (cached pool for user callbacks, gRPCDirectExecutor)