@@ -2,6 +2,7 @@ package acp
22
33import (
44 "context"
5+ "encoding/json"
56 "io"
67 "slices"
78 "sync"
@@ -467,6 +468,59 @@ func TestConnectionHandlesNotifications(t *testing.T) {
467468 }
468469}
469470
471+ func TestConnection_DoesNotCancelInboundContextBeforeDrainingNotificationsOnDisconnect (t * testing.T ) {
472+ const n = 25
473+
474+ incomingR , incomingW := io .Pipe ()
475+
476+ var (
477+ wg sync.WaitGroup
478+ canceledCount atomic.Int64
479+ )
480+ wg .Add (n )
481+
482+ c := NewConnection (func (ctx context.Context , method string , _ json.RawMessage ) (any , * RequestError ) {
483+ defer wg .Done ()
484+ // Slow down processing so some notifications are handled after the receive
485+ // loop observes EOF and signals disconnect.
486+ time .Sleep (10 * time .Millisecond )
487+ if ctx .Err () != nil {
488+ canceledCount .Add (1 )
489+ }
490+ return nil , nil
491+ }, io .Discard , incomingR )
492+
493+ // Write notifications quickly and then close the stream to simulate a peer disconnect.
494+ for i := 0 ; i < n ; i ++ {
495+ if _ , err := io .WriteString (incomingW , `{"jsonrpc":"2.0","method":"test/notify","params":{}}` + "\n " ); err != nil {
496+ t .Fatalf ("write notification: %v" , err )
497+ }
498+ }
499+ _ = incomingW .Close ()
500+
501+ select {
502+ case <- c .Done ():
503+ // Expected: peer disconnect observed promptly.
504+ case <- time .After (2 * time .Second ):
505+ t .Fatalf ("timeout waiting for connection Done()" )
506+ }
507+
508+ done := make (chan struct {})
509+ go func () {
510+ wg .Wait ()
511+ close (done )
512+ }()
513+ select {
514+ case <- done :
515+ case <- time .After (3 * time .Second ):
516+ t .Fatalf ("timeout waiting for notification handlers" )
517+ }
518+
519+ if got := canceledCount .Load (); got != 0 {
520+ t .Fatalf ("inbound handler context was canceled for %d/%d notifications" , got , n )
521+ }
522+ }
523+
470524// Test initialize method behavior
471525func TestConnectionHandlesInitialize (t * testing.T ) {
472526 c2aR , c2aW := io .Pipe ()
0 commit comments