@@ -514,24 +514,25 @@ func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *Client
514514 }
515515 }
516516
517- if desc != unaryStreamDesc {
518- // Listen on cc and stream contexts to cleanup when the user closes the
519- // ClientConn or cancels the stream context. In all other cases, an error
520- // should already be injected into the recv buffer by the transport, which
521- // the client will eventually receive, and then we will cancel the stream's
522- // context in clientStream.finish.
523- go func () {
524- select {
525- case <- cc .ctx .Done ():
526- cs .finish (ErrClientConnClosing )
527- case <- ctx .Done ():
528- cs .finish (toRPCErr (ctx .Err ()))
529- }
530- }()
531- }
517+ cs .registerContextCallbacks ()
532518 return cs , nil
533519}
534520
521+ // registerContextCallbacks registers callbacks to clean up the stream when the
522+ // RPC context expires or the ClientConn is closed.
523+ func (cs * clientStream ) registerContextCallbacks () {
524+ cs .mu .Lock ()
525+ defer cs .mu .Unlock ()
526+ if ! cs .finished {
527+ cs .stopCtx = context .AfterFunc (cs .ctx , func () {
528+ cs .finish (toRPCErr (cs .ctx .Err ()))
529+ })
530+ cs .stopCC = context .AfterFunc (cs .cc .ctx , func () {
531+ cs .finish (ErrClientConnClosing )
532+ })
533+ }
534+ }
535+
535536// newAttemptLocked creates a new csAttempt without a transport or stream.
536537func (cs * clientStream ) newAttemptLocked (isTransparent bool ) (* csAttempt , error ) {
537538 if err := cs .ctx .Err (); err != nil {
@@ -713,6 +714,9 @@ type clientStream struct {
713714 replayBuffer []replayOp // operations to replay on retry
714715 replayBufferSize int // current size of replayBuffer
715716
717+ stopCtx func () bool // stops the callback registered to run on RPC context expiration
718+ stopCC func () bool // stops the callback registered to run on ClientConn context expiration
719+
716720 // Bool fields are grouped at the tail to eliminate the alignment padding
717721 // that would otherwise follow each bool when the next field is pointer- or
718722 // int-sized. See https://github.com/grpc/grpc-go/issues/9280 for benchmarks.
@@ -1195,6 +1199,10 @@ func (cs *clientStream) finish(err error) {
11951199 return
11961200 }
11971201 cs .finished = true
1202+ stopCtx := cs .stopCtx
1203+ stopCC := cs .stopCC
1204+ cs .stopCtx = nil
1205+ cs .stopCC = nil
11981206 cs .commitAttemptLocked ()
11991207 attemptCreated := cs .attempt != nil
12001208 if attemptCreated {
@@ -1208,6 +1216,12 @@ func (cs *clientStream) finish(err error) {
12081216 }
12091217
12101218 cs .mu .Unlock ()
1219+ if stopCtx != nil {
1220+ stopCtx ()
1221+ }
1222+ if stopCC != nil {
1223+ stopCC ()
1224+ }
12111225 // Only one of cancel or trailer needs to be logged.
12121226 if len (cs .binlogs ) != 0 {
12131227 switch err {
@@ -1472,29 +1486,28 @@ func newNonRetryClientStream(ctx context.Context, desc *StreamDesc, method strin
14721486 as .transportStream = s
14731487 as .parser = parser {r : s , bufferPool : ac .dopts .copts .BufferPool }
14741488 ac .incrCallsStarted ()
1475- if desc != unaryStreamDesc {
1476- // Listen on stream context to cleanup when the stream context is
1477- // canceled. Also listen for the addrConn's context in case the
1478- // addrConn is closed or reconnects to a different address. In all
1479- // other cases, an error should already be injected into the recv
1480- // buffer by the transport, which the client will eventually receive,
1481- // and then we will cancel the stream's context in
1482- // addrConnStream.finish.
1483- go func () {
1484- ac .mu .Lock ()
1485- acCtx := ac .ctx
1486- ac .mu .Unlock ()
1487- select {
1488- case <- acCtx .Done ():
1489- as .finish (status .Error (codes .Canceled , "grpc: the SubConn is closing" ))
1490- case <- ctx .Done ():
1491- as .finish (toRPCErr (ctx .Err ()))
1492- }
1493- }()
1494- }
1489+ as .registerContextCallbacks ()
14951490 return & clientStreamWrapper {ClientStream : as , desc : desc }, nil
14961491}
14971492
1493+ // registerContextCallbacks registers callbacks to clean up the stream when the
1494+ // RPC context expires or the SubConn is closed.
1495+ func (as * addrConnStream ) registerContextCallbacks () {
1496+ as .mu .Lock ()
1497+ defer as .mu .Unlock ()
1498+ if ! as .finished {
1499+ as .stopCtx = context .AfterFunc (as .ctx , func () {
1500+ as .finish (toRPCErr (as .ctx .Err ()))
1501+ })
1502+ as .ac .mu .Lock ()
1503+ acCtx := as .ac .ctx
1504+ as .ac .mu .Unlock ()
1505+ as .stopCC = context .AfterFunc (acCtx , func () {
1506+ as .finish (status .Error (codes .Canceled , "grpc: the SubConn is closing" ))
1507+ })
1508+ }
1509+ }
1510+
14981511type addrConnStream struct {
14991512 transportStream * transport.ClientStream
15001513 ac * addrConn
@@ -1515,6 +1528,9 @@ type addrConnStream struct {
15151528 mu sync.Mutex
15161529 parser parser
15171530
1531+ stopCtx func () bool // stops the callback registered to run on RPC context expiration
1532+ stopCC func () bool // stops the callback registered to run on SubConn context expiration
1533+
15181534 // Bool fields are grouped at the tail to eliminate the alignment padding
15191535 // that would otherwise follow each bool when the next field is pointer- or
15201536 // int-sized. See https://github.com/grpc/grpc-go/issues/9348 for benchmarks.
@@ -1656,6 +1672,11 @@ func (as *addrConnStream) finish(err error) {
16561672 return
16571673 }
16581674 as .finished = true
1675+ stopCtx := as .stopCtx
1676+ stopCC := as .stopCC
1677+ as .stopCtx = nil
1678+ as .stopCC = nil
1679+
16591680 if err == io .EOF {
16601681 // Ending a stream with EOF indicates a success.
16611682 err = nil
@@ -1671,6 +1692,13 @@ func (as *addrConnStream) finish(err error) {
16711692 }
16721693 as .cancel ()
16731694 as .mu .Unlock ()
1695+
1696+ if stopCtx != nil {
1697+ stopCtx ()
1698+ }
1699+ if stopCC != nil {
1700+ stopCC ()
1701+ }
16741702}
16751703
16761704// ServerStream defines the server-side behavior of a streaming RPC.
0 commit comments