@@ -24,6 +24,7 @@ import (
2424 "errors"
2525 "fmt"
2626 "io"
27+ "math"
2728 "slices"
2829 "strings"
2930 "sync"
@@ -1513,33 +1514,13 @@ func (cs *clientStream) recvFromProcServerLoop(newStream func(context.Context, .
15131514 }
15141515
15151516 if windowUpdate := resp .GetServerWindowUpdate (); windowUpdate != nil {
1516- deltaDownstreamToSidestream := windowUpdate .GetWindowIncrementDownstreamToSidestream ()
1517- if deltaDownstreamToSidestream != 0 {
1518- // Add the window update to the existing window.
1519- newQuota := cs .downstreamToSidestreamWindow .Add (deltaDownstreamToSidestream )
1520- previousQuota := newQuota - deltaDownstreamToSidestream
1521- // If the window turned from negative to positive, send a signal to the
1522- // `acquireDownstreamToSidestreamWindow` function to wake it up.
1523- if previousQuota <= 0 && newQuota > 0 {
1524- select {
1525- case cs .downstreamToSidestreamPositiveUpdateCh <- struct {}{}:
1526- default :
1527- }
1528- }
1517+ if err := applyServerWindowUpdate (& cs .downstreamToSidestreamWindow , cs .downstreamToSidestreamPositiveUpdateCh , windowUpdate .GetWindowIncrementDownstreamToSidestream ()); err != nil {
1518+ cs .failProcStream (fmt .Errorf ("external processor sent an invalid downstream-to-sidestream window update: %v" , err ))
1519+ return
15291520 }
1530- deltaUpstreamToSidestream := windowUpdate .GetWindowIncrementUpstreamToSidestream ()
1531- if deltaUpstreamToSidestream != 0 {
1532- // Add the window update to the existing window.
1533- newQuota := cs .upstreamToSidestreamWindow .Add (deltaUpstreamToSidestream )
1534- previousQuota := newQuota - deltaUpstreamToSidestream
1535- // If the window turned from negative to positive, send a signal to the
1536- // `acquireUpstreamToSidestreamWindow` function to wake it up.
1537- if previousQuota <= 0 && newQuota > 0 {
1538- select {
1539- case cs .upstreamToSidestreamPositiveUpdateCh <- struct {}{}:
1540- default :
1541- }
1542- }
1521+ if err := applyServerWindowUpdate (& cs .upstreamToSidestreamWindow , cs .upstreamToSidestreamPositiveUpdateCh , windowUpdate .GetWindowIncrementUpstreamToSidestream ()); err != nil {
1522+ cs .failProcStream (fmt .Errorf ("external processor sent an invalid upstream-to-sidestream window update: %v" , err ))
1523+ return
15431524 }
15441525 }
15451526
@@ -2057,6 +2038,43 @@ func (cs *clientStream) waitForTrailerProcessing(recvErr error) error {
20572038 }
20582039}
20592040
2041+ // applyServerWindowUpdate adds a flow control window increment received from
2042+ // the external processor to window, waking a blocked acquirer if the window
2043+ // becomes positive. The increment is server-controlled, so it must be
2044+ // non-negative and must not overflow the int64 window; an increment that
2045+ // violates either is a protocol error and returns one instead of wrapping the
2046+ // accounting, matching how HTTP/2 flow control rejects a window update that
2047+ // exceeds the maximum. This recv loop is the only writer that grows the
2048+ // window, so the compare-and-swap only contends with a concurrent deduction in
2049+ // acquire{Downstream,Upstream}ToSidestreamWindow.
2050+ func applyServerWindowUpdate (window * atomic.Int64 , positiveUpdateCh chan struct {}, delta int64 ) error {
2051+ if delta == 0 {
2052+ return nil
2053+ }
2054+ if delta < 0 {
2055+ return fmt .Errorf ("negative window increment %d" , delta )
2056+ }
2057+ for {
2058+ previousQuota := window .Load ()
2059+ if previousQuota > math .MaxInt64 - delta {
2060+ return fmt .Errorf ("window increment %d overflows the current window %d" , delta , previousQuota )
2061+ }
2062+ newQuota := previousQuota + delta
2063+ if ! window .CompareAndSwap (previousQuota , newQuota ) {
2064+ continue
2065+ }
2066+ // If the window turned from non-positive to positive, wake the acquirer
2067+ // blocked in acquire{Downstream,Upstream}ToSidestreamWindow.
2068+ if previousQuota <= 0 && newQuota > 0 {
2069+ select {
2070+ case positiveUpdateCh <- struct {}{}:
2071+ default :
2072+ }
2073+ }
2074+ return nil
2075+ }
2076+ }
2077+
20602078// acquireDownstreamToSidestreamWindow checks available flow control window for
20612079// downstream to sidestream. If window is positive, it deducts bodySize and
20622080// returns true immediately. If window is <= 0, it blocks until a positive
0 commit comments