Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,24 @@
* vRPC transport.
*/
public interface Session {
/** State transitions of Session, will happen in defined order */
/**
* State transitions of Session, will happen in defined order:
*
* <ul>
* <li>{@link #NEW}: Initial state before {@link #start} is called.
* <li>{@link #STARTING}: Open handshake sent; bounded by {@code OPEN_SESSION_TIMEOUT}.
* <li>{@link #READY}: Open handshake completed; ready to serve vRPCs.
* <li>{@link #CLOSING}: Graceful close initiated (via {@link #close} or server {@code
* GoAwayResponse}) while an in-flight vRPC (including a client-cancelled one) is still on
* the wire waiting for the server's response before {@code CloseSessionRequest} can be
* sent. Bounded by server heartbeats ({@code checkHeartbeat}) and the server-enforced
* {@code VirtualRpcRequest.deadline}.
* <li>{@link #WAIT_SERVER_CLOSE}: No vRPC is in flight on the wire (so server heartbeats stop)
* and {@code CloseSessionRequest} + {@code halfClose()} have been sent; waiting for the
Comment thread
igorbernstein2 marked this conversation as resolved.
Resolved
* server to close the stream. Bounded by {@code CLOSE_SESSION_TIMEOUT}.
* <li>{@link #CLOSED}: Terminal state; underlying stream is closed.
* </ul>
*/
enum SessionState {
NEW(0),
STARTING(1),
Expand Down Expand Up @@ -95,12 +112,14 @@ OpenParams withIncrementedAttempts() {
void start(OpenSessionRequest req, Metadata headers, Listener sessionListener);

/**
* Caller instructed close of the Session. Will immediately close the session and abort all
* outstanding vRPCs.
* Initiates a graceful close of the Session. Transitions the session to {@link
* SessionState#CLOSING}, waits for any in-flight vRPC on the wire (including a client-cancelled
* one) to receive its server response, and then sends {@link CloseSessionRequest} ({@link
* SessionState#WAIT_SERVER_CLOSE}).
*/
void close(CloseSessionRequest req);

/** Force close a session. */
/** Immediately aborts the session stream and fails any in-flight vRPC. */
void forceClose(CloseSessionRequest reason);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ public void close(CloseSessionRequest req) {
closeReason = req;
updateState(SessionState.CLOSING);

// If a vRPC is still in flight on the wire (even if cancelled locally via cancelRpc),
// wait in CLOSING for the server's vRPC response before sending CloseSessionRequest:
// the protocol has no wire cancel-vRPC message yet, and sending CloseSessionRequest while
// the server still has an active vRPC logs a server-side blackholed-response error.
if (currentRpc == null) {
startGracefulClose();
}
Expand Down Expand Up @@ -493,6 +497,17 @@ public void startRpc(VRpcImpl<?, ?, ?> rpc, VirtualRpcRequest payload) {
public void cancelRpc(long rpcId, @Nullable String message, @Nullable Throwable cause) {
sessionSyncContext.execute(
() -> {
// The Session protocol does not yet have a wire cancel-vRPC message, so the server
// continues executing the vRPC until it finishes or hits the server-enforced
// VirtualRpcRequest.deadline. We record currentCancel here while keeping currentRpc set
// until handleVRpcResponse / handleVRpcErrorResponse arrives so that:
// 1. A READY session is not returned to the pool while the server still has an active
// vRPC on it (which would violate the single-in-flight-vRPC limit on the next call).
// 2. A CLOSING session waits for the server to finish the vRPC before sending
// CloseSessionRequest (sending CloseSessionRequest while a vRPC is still active on
// the server is treated as a blackholed-response error by the server).
// Note: the caller's operation future is already completed immediately by
// RetryingVRpc.cancel().
if (currentRpc != null && rpcId == currentRpc.rpcId) {
currentCancel =
VRpcResult.createRejectedError(
Expand Down Expand Up @@ -556,6 +571,15 @@ private void cancelCloseTimeout() {
// Runs on sessionSyncContext (dispatched from the wheel-timer tick body). If the session is
// still in WAIT_SERVER_CLOSE, the server never closed the stream after CloseSessionRequest;
// force-close so the session reaches CLOSED.
//
// Why this only covers WAIT_SERVER_CLOSE and not CLOSING:
// - A session only stays in CLOSING while currentRpc != null (an active vRPC is on the wire).
// - Server invariant: the server only sends HeartbeatResponse while a vRPC is actively
// running on the server, and enforces VirtualRpcRequest.deadline on every vRPC.
// - Therefore, CLOSING is already bounded by checkHeartbeat() (if the server dies/stalls) and
// the server-enforced vRPC deadline (if the server is alive and sending heartbeats).
// - Once the vRPC finishes and the session enters WAIT_SERVER_CLOSE, no vRPC is active so
// server heartbeats stop, and closeTimeout takes over to bound waiting for stream closure.
private void checkCloseTimeout() {
sessionSyncContext.throwIfNotInThisSynchronizationContext();
if (state != SessionState.WAIT_SERVER_CLOSE) {
Expand Down
Loading