@@ -52,16 +52,19 @@ func (s *Session) claimSlot(rpc *vrpcImpl) bool {
5252// markCancelled records ctx.Done cancellation of rpc without freeing the
5353// slot — the caller returns, but activeRPC stays until the server response
5454// arrives to drain it. First-cancel-wins; a racing drain that clears
55- // activeRPC makes this a no-op.
56- func (s * Session ) markCancelled (rpc * vrpcImpl , res vrpcResult ) {
55+ // activeRPC makes this a no-op. Returns whether rpc was still the active
56+ // slot occupant, so callers that also want the "still in flight?" signal
57+ // don't have to re-acquire slotMu with a separate activeVRPC() call.
58+ func (s * Session ) markCancelled (rpc * vrpcImpl , res vrpcResult ) (stillActive bool ) {
5759 s .slotMu .Lock ()
5860 defer s .slotMu .Unlock ()
5961 if s .activeRPC != rpc {
60- return
62+ return false
6163 }
6264 if s .currentCancel == nil {
6365 s .currentCancel = & res
6466 }
67+ return true
6568}
6669
6770// drainSlot atomically clears the (activeRPC, currentCancel) pair iff
@@ -134,7 +137,7 @@ func (s *Session) Invoke(ctx context.Context, desc VRpcDescriptor, req interface
134137 if attempt > 1 {
135138 s .noteRetryAttempt (ctx , desc .Method (), attempt )
136139 }
137- sessionReq := buildInvokeRequest (rpcID , reqBytes , attempt , startTime , ctx )
140+ sessionReq := buildInvokeRequest (ctx , rpcID , reqBytes , attempt , startTime )
138141
139142 // Capture SentAt immediately before the frame is handed to Send so
140143 // downstream metrics can compute client-side blocking latency as
@@ -185,7 +188,7 @@ func (s *Session) noteRetryAttempt(ctx context.Context, method string, attempt i
185188 prevCode := status .Code (prev ).String ()
186189 s .debugf ("retry attempt=%d method=%s prev_code=%s prev_err=%v" ,
187190 attempt , method , prevCode , prev )
188- s .recordEvent ("retry" , "attempt=%d method=%s prev_code=%s prev_err=%v" ,
191+ s .recordEvent (SessionEventRetry , "attempt=%d method=%s prev_code=%s prev_err=%v" ,
189192 attempt , method , prevCode , prev )
190193}
191194
@@ -195,7 +198,7 @@ func (s *Session) noteRetryAttempt(ctx context.Context, method string, attempt i
195198// so the server measures from receive time rather than an absolute wall
196199// clock. Omitted when ctx has no deadline or the budget is already
197200// non-positive (the client-side ctx.Done branch will fire immediately).
198- func buildInvokeRequest (rpcID int64 , reqBytes []byte , attempt int64 , startTime time.Time , ctx context. Context ) * spb.SessionRequest {
201+ func buildInvokeRequest (ctx context. Context , rpcID int64 , reqBytes []byte , attempt int64 , startTime time.Time ) * spb.SessionRequest {
199202 virtRpc := & spb.VirtualRpcRequest {
200203 RpcId : rpcID ,
201204 Payload : reqBytes ,
@@ -232,15 +235,15 @@ func (s *Session) awaitInvokeResult(ctx context.Context, rpc *vrpcImpl, desc VRp
232235 case <- ctx .Done ():
233236 select {
234237 case res := <- rpc .resultChan :
235- return s .processResult (rpc , desc , result , res )
238+ return s .processResult (desc , result , res )
236239 default :
237240 }
238- s .recordCtxDone (ctx , rpc , desc .Method (), result .SentAt )
239241 cancelErr := tagErr (StateTransportFailure , ctx .Err ())
240- s .markCancelled (rpc , vrpcResult {err : cancelErr })
242+ stillActive := s .markCancelled (rpc , vrpcResult {err : cancelErr })
243+ s .recordCtxDone (ctx , rpc , desc .Method (), result .SentAt , stillActive )
241244 return cancelErr
242245 case res := <- rpc .resultChan :
243- return s .processResult (rpc , desc , result , res )
246+ return s .processResult (desc , result , res )
244247 }
245248}
246249
@@ -252,7 +255,7 @@ func (s *Session) awaitInvokeResult(ctx context.Context, rpc *vrpcImpl, desc VRp
252255// The res.resp.RpcId == rpc.id check that used to live here is gone:
253256// under slotMu, handleVRPCResponse gates the id match BEFORE drainSlot,
254257// so deliver can only ever put a matching-id response into resultChan.
255- func (s * Session ) processResult (rpc * vrpcImpl , desc VRpcDescriptor , result * InvokeResult , res vrpcResult ) error {
258+ func (s * Session ) processResult (desc VRpcDescriptor , result * InvokeResult , res vrpcResult ) error {
256259 result .TransportLatency = time .Since (result .SentAt )
257260 ci := res .ClusterInfo ()
258261 result .ClusterInfo = ci
@@ -274,22 +277,21 @@ func (s *Session) processResult(rpc *vrpcImpl, desc VRpcDescriptor, result *Invo
274277 if res .resp .Stats != nil && res .resp .Stats .BackendLatency != nil {
275278 s .recordLatency (res .resp .Stats .BackendLatency .AsDuration ())
276279 }
277- _ = rpc // rpc kept in signature for future per-rpc metrics; no reads today.
278280 return nil
279281}
280282
281283// recordCtxDone emits the debug + sessionz event for a ctx cancellation
282- // or deadline fire while a vRPC was in flight. Captures whether the RPC
283- // was still holding the slot at cancel time — useful for spotting races
284- // between our cancel and a late server response.
285- func ( s * Session ) recordCtxDone ( ctx context. Context , rpc * vrpcImpl , method string , sentAt time. Time ) {
286- stillActive := s . activeVRPC () == rpc
284+ // or deadline fire while a vRPC was in flight. stillActive comes from
285+ // markCancelled's return so no second slotMu take is needed here — it
286+ // reports whether rpc still held the slot at cancel time, useful for
287+ // spotting races between our cancel and a late server response.
288+ func ( s * Session ) recordCtxDone ( ctx context. Context , rpc * vrpcImpl , method string , sentAt time. Time , stillActive bool ) {
287289 sessState := State (s .state .Load ())
288290 waited := time .Since (sentAt )
289291 peer := s .peerInfoSummary ()
290292 s .debugf ("vRPC %s rpc_id=%d ctx.Done waited=%v err=%v session_state=%v still_in_flight=%v %s" ,
291293 method , rpc .id , waited , ctx .Err (), sessState , stillActive , peer )
292- s .recordEvent ("ctx-done" , "method=%s rpc_id=%d waited=%v err=%v session_state=%v still_in_flight=%v %s" ,
294+ s .recordEvent (SessionEventCtxDone , "method=%s rpc_id=%d waited=%v err=%v session_state=%v still_in_flight=%v %s" ,
293295 method , rpc .id , waited , ctx .Err (), sessState , stillActive , peer )
294296}
295297
@@ -353,7 +355,9 @@ func (s *Session) routeVRPCFrame(rpcID int64, frameName, nilTag string, counter
353355 // resultChan. Just count the drain for observability.
354356 recordDebugTag (tagSessionVRPCCancelledDrained )
355357 } else {
356- s .deliver (drained , result )
358+ // resultChan is cap-1 and drainSlot serialized this write; the
359+ // send never blocks.
360+ drained .resultChan <- result
357361 }
358362 // v3: drainSlot success is the sole "session became free" signal.
359363 // Fires on every drain (not just the cancelled branch) so the pool
@@ -386,17 +390,6 @@ func errorResponseToErr(errResp *spb.ErrorResponse) error {
386390 return st .Err ()
387391}
388392
389- // deliver writes a result onto the RPC's buffered (cap 1) channel.
390- // Under slotMu, exactly one caller ever holds a drained rpc (the
391- // winning drainSlot inside handleVRPCResponse / handleVRPCErrorResponse
392- // / cancelActiveRPCs), so the two-writers race on resultChan is
393- // impossible in production. The cap-1 buffer is retained as defense
394- // against the awaitInvokeResult ctx.Done-vs-response tick race — the
395- // send must not block if the reader stopped listening.
396- func (s * Session ) deliver (rpc * vrpcImpl , res vrpcResult ) {
397- rpc .resultChan <- res
398- }
399-
400393// ForceClose transitions the session straight to StateClosed and cancels
401394// any in-flight vRPC with a TransportFailure-tagged error. Minimal port
402395// from the sessionz-debug lifecycle body — the full teardown (setCloseReason,
@@ -442,5 +435,5 @@ func (s *Session) cancelActiveRPCs(err error) {
442435 // / benign shutdown while an RPC was in-flight. Server may or may not
443436 // have processed — TransportFailure classification lets idempotent ops
444437 // retry and prevents non-idempotent ones from double-applying.
445- s . deliver ( drained , vrpcResult {err : tagErr (StateTransportFailure , err )})
438+ drained . resultChan <- vrpcResult {err : tagErr (StateTransportFailure , err )}
446439}
0 commit comments