parent
3486f6bcaf
commit
cd7d80c591
1 changed files with 1368 additions and 104 deletions
|
|
@ -208,12 +208,30 @@ type ConnectionConfig struct {
|
|||
Bind func(*Connection) Handler // required
|
||||
OnDone func() // optional
|
||||
OnInternalError func(error) // optional
|
||||
|
||||
// PropagateCancellation controls whether cancellation of the context
|
||||
// passed to [NewConnection] is observable by request handlers.
|
||||
//
|
||||
// By default (false), the connection wraps that context (see [notDone])
|
||||
// so handlers' Done channels do not fire when the connection's root
|
||||
// context is cancelled. Cancellation of an in-flight handler is then
|
||||
// expected to flow only through the jsonrpc2 layer's explicit channels
|
||||
// (the [Preempter] reacting to the peer's cancel notification, or a
|
||||
// transport read/write failure cancelling every in-flight request).
|
||||
//
|
||||
// Set this true when cancellation of the connection's context is itself
|
||||
// a meaningful signal that handlers should react to (for example, when
|
||||
// the connection is tied to a carrier that owns it and whose end means
|
||||
// the request is cancelled).
|
||||
PropagateCancellation bool // optional
|
||||
}
|
||||
|
||||
// NewConnection creates a new [Connection] object and starts processing
|
||||
// incoming messages.
|
||||
func NewConnection(ctx context.Context, cfg ConnectionConfig) *Connection {
|
||||
ctx = notDone{ctx}
|
||||
if !cfg.PropagateCancellation {
|
||||
ctx = notDone{ctx}
|
||||
}
|
||||
|
||||
c := &Connection{
|
||||
state: inFlightState{closer: cfg.Closer},
|
||||
|
|
@ -530,6 +548,14 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter
|
|||
ac.retire(&Response{ID: id, Error: err})
|
||||
}
|
||||
s.outgoingCalls = nil
|
||||
|
||||
// Cancel any incoming requests still in flight: with the reader gone we
|
||||
// cannot receive cancellation notifications, and likely cannot write a
|
||||
// response either, so parked handlers have nothing useful left to do.
|
||||
// Mirrors the equivalent cleanup on write failure.
|
||||
for _, r := range s.incomingByID {
|
||||
r.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -724,9 +750,12 @@ func (c *Connection) write(ctx context.Context, msg Message) error {
|
|||
var err error
|
||||
// Fail writes immediately if the connection is shutting down.
|
||||
//
|
||||
// TODO(rfindley): should we allow cancellation notifications through? It
|
||||
// could be the case that writes can still succeed.
|
||||
// Allow outgoing "notifications" forwarded by the Notify method.
|
||||
// This will allow to send the cancelled notification when the client is shutting down.
|
||||
c.updateInFlight(func(s *inFlightState) {
|
||||
if req, ok := msg.(*Request); ok && !req.IsCall() && s.outgoingNotifications > 0 {
|
||||
return
|
||||
}
|
||||
err = s.shuttingDown(ErrServerClosing)
|
||||
})
|
||||
if err == nil {
|
||||
|
|
@ -771,6 +800,15 @@ func (c *Connection) internalErrorf(format string, args ...any) error {
|
|||
}
|
||||
|
||||
// notDone is a context.Context wrapper that returns a nil Done channel.
|
||||
//
|
||||
// Request handlers' contexts are derived from the connection's root context,
|
||||
// which by default is wrapped in notDone so a transport-level cancellation
|
||||
// does not implicitly cancel every in-flight handler. Cancellation of an
|
||||
// in-flight handler is instead expected to flow only through the jsonrpc2
|
||||
// layer's explicit channels: the [Preempter] calling [Connection.Cancel] in
|
||||
// response to the peer's cancel notification, or the transport itself
|
||||
// failing (the read loop exits on EOF or a write fails) — both of which
|
||||
// cancel every in-flight incoming request in turn.
|
||||
type notDone struct{ ctx context.Context }
|
||||
|
||||
func (ic notDone) Value(key any) any {
|
||||
|
|
|
|||
Loading…
Reference in a new issue