Skip to content

Commit 2e16971

Browse files
committed
internal/jsonrpc2: plumb cancellation cause through request context
handleAsync reported why a request was canceled by inspecting the connection-level s.writeErr, guarded by a TODO awaiting golang/go#51365. That API shipped as context.WithCancelCause/context.Cause in Go 1.21, and this module targets Go 1.25. Carry the cancellation cause on each request's context instead of inferring it from global state. Both the write-failure and read-side teardowns cancel each in-flight request with a cause wrapping ErrServerClosing; the remaining cancel sites (peer cancel, normal completion) pass nil. handleAsync now reads context.Cause(req.ctx) instead of guessing from s.writeErr. This also fixes a latent mis-attribution: s.writeErr is connection-level, so a request canceled for an unrelated reason was reported as ErrServerClosing whenever s.writeErr happened to be non-nil. ctx.Err() still returns context.Canceled in all cases, so external behavior for callers inspecting ctx.Err() is unchanged.
1 parent ce393ac commit 2e16971

1 file changed

Lines changed: 13 additions & 14 deletions

File tree

internal/jsonrpc2/conn.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func (s *inFlightState) shuttingDown(errClosing error) error {
172172
type incomingRequest struct {
173173
*Request // the request being processed
174174
ctx context.Context
175-
cancel context.CancelFunc
175+
cancel context.CancelCauseFunc
176176
}
177177

178178
// Reader abstracts the transport mechanics from the JSON RPC protocol.
@@ -458,7 +458,7 @@ func (c *Connection) Cancel(id ID) {
458458
req = s.incomingByID[id]
459459
})
460460
if req != nil {
461-
req.cancel()
461+
req.cancel(nil)
462462
}
463463
}
464464

@@ -554,7 +554,7 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter
554554
// response either, so parked handlers have nothing useful left to do.
555555
// Mirrors the equivalent cleanup on write failure.
556556
for _, r := range s.incomingByID {
557-
r.cancel()
557+
r.cancel(fmt.Errorf("%w: %v", ErrServerClosing, err))
558558
}
559559
})
560560
}
@@ -564,7 +564,7 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter
564564
func (c *Connection) acceptRequest(ctx context.Context, msg *Request, preempter Preempter) {
565565
// In theory notifications cannot be cancelled, but we build them a cancel
566566
// context anyway.
567-
reqCtx, cancel := context.WithCancel(ctx)
567+
reqCtx, cancel := context.WithCancelCause(ctx)
568568
req := &incomingRequest{
569569
Request: msg,
570570
ctx: reqCtx,
@@ -665,15 +665,14 @@ func (c *Connection) handleAsync() {
665665
return
666666
}
667667

668-
// Only deliver to the Handler if not already canceled.
668+
// Only deliver to the Handler if not already canceled. If the request
669+
// was canceled with a cause (e.g. a write failure cancels every
670+
// in-flight request), report that cause rather than the bare
671+
// context.Canceled.
669672
if err := req.ctx.Err(); err != nil {
670-
c.updateInFlight(func(s *inFlightState) {
671-
if s.writeErr != nil {
672-
// Assume that req.ctx was canceled due to s.writeErr.
673-
// TODO(#51365): use a Context API to plumb this through req.ctx.
674-
err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr)
675-
}
676-
})
673+
if cause := context.Cause(req.ctx); cause != nil {
674+
err = cause
675+
}
677676
c.processResult("handleAsync", req, nil, err)
678677
continue
679678
}
@@ -734,7 +733,7 @@ func (c *Connection) processResult(from any, req *incomingRequest, result any, e
734733
}
735734

736735
// Cancel the request to free any associated resources.
737-
req.cancel()
736+
req.cancel(nil)
738737
c.updateInFlight(func(s *inFlightState) {
739738
if s.incoming == 0 {
740739
panic("jsonrpc2: processResult called when incoming count is already zero")
@@ -777,7 +776,7 @@ func (c *Connection) write(ctx context.Context, msg Message) error {
777776
if s.writeErr == nil {
778777
s.writeErr = err
779778
for _, r := range s.incomingByID {
780-
r.cancel()
779+
r.cancel(fmt.Errorf("%w: %v", ErrServerClosing, err))
781780
}
782781
}
783782
})

0 commit comments

Comments
 (0)