@@ -131,6 +131,7 @@ type serverOptions struct {
131
131
connectionTimeout time.Duration
132
132
maxHeaderListSize * uint32
133
133
headerTableSize * uint32
134
+ numStreamWorkers uint32
134
135
}
135
136
136
137
var defaultServerOptions = serverOptions {
@@ -388,6 +389,22 @@ func HeaderTableSize(s uint32) ServerOption {
388
389
})
389
390
}
390
391
392
+ // NumStreamWorkers returns a ServerOption that sets the number of worker
393
+ // goroutines that should be used to process incoming streams. Setting this to
394
+ // zero (default) will disable workers and spawn a new goroutine for each
395
+ // stream.
396
+ //
397
+ // This API is EXPERIMENTAL.
398
+ func NumStreamWorkers (numStreamWorkers uint32 ) ServerOption {
399
+ // TODO: If/when this API gets stabilized (i.e. stream workers become the
400
+ // only way streams are processed), change the behavior of the zero value to
401
+ // a sane default. Preliminary experiments suggest that a value equal to the
402
+ // number of CPUs available is most performant; requires thorough testing.
403
+ return newFuncServerOption (func (o * serverOptions ) {
404
+ o .numStreamWorkers = numStreamWorkers
405
+ })
406
+ }
407
+
391
408
// NewServer creates a gRPC server which has no service registered and has not
392
409
// started to accept requests yet.
393
410
func NewServer (opt ... ServerOption ) * Server {
@@ -712,17 +729,6 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr
712
729
return st
713
730
}
714
731
715
- func floorCPUCount () uint32 {
716
- n := uint32 (runtime .NumCPU ())
717
- for i := uint32 (1 << 31 ); i >= 2 ; i >>= 1 {
718
- if n & i > 0 {
719
- return i
720
- }
721
- }
722
-
723
- return 1
724
- }
725
-
726
732
// workerStackReset defines how often the stack must be reset. Every N
727
733
// requests, by spawning a new goroutine in its place, a worker can reset its
728
734
// stack so that large stacks don't live in memory forever. 2^16 should allow
@@ -749,34 +755,36 @@ func (s *Server) streamWorker(st transport.ServerTransport, wg *sync.WaitGroup,
749
755
}
750
756
}
751
757
752
- // numWorkers defines the number of stream handling workers. After experiments
753
- // with different CPU counts, using the floor of the number of CPUs available
754
- // was found to be the number optimal for performance across the board (QPS,
755
- // latency).
756
- var numWorkers = floorCPUCount ()
757
-
758
- // workerMask is used to perform bitwise AND operations instead of expensive
759
- // module operations on integers.
760
- var workerMask = numWorkers - 1
761
-
762
758
func (s * Server ) serveStreams (st transport.ServerTransport ) {
763
759
defer st .Close ()
764
760
var wg sync.WaitGroup
765
761
766
- streamChannels := make ([]chan * transport.Stream , numWorkers )
767
- for i := range streamChannels {
768
- streamChannels [i ] = make (chan * transport.Stream )
769
- go s .streamWorker (st , & wg , streamChannels [i ])
762
+ var streamChannels []chan * transport.Stream
763
+ if s .opts .numStreamWorkers > 0 {
764
+ streamChannels = make ([]chan * transport.Stream , s .opts .numStreamWorkers )
765
+ for i := range streamChannels {
766
+ streamChannels [i ] = make (chan * transport.Stream )
767
+ go s .streamWorker (st , & wg , streamChannels [i ])
768
+ }
770
769
}
771
770
771
+ var streamChannelCounter uint32
772
772
st .HandleStreams (func (stream * transport.Stream ) {
773
773
wg .Add (1 )
774
- select {
775
- case streamChannels [stream .ID ()& workerMask ] <- stream :
776
- default :
774
+ if s .opts .numStreamWorkers > 0 {
775
+ select {
776
+ case streamChannels [atomic .AddUint32 (& streamChannelCounter , 1 )% s .opts .numStreamWorkers ] <- stream :
777
+ default :
778
+ // If all stream workers are busy, fallback to default code path.
779
+ go func () {
780
+ s .handleStream (st , stream , s .traceInfo (st , stream ))
781
+ wg .Done ()
782
+ }()
783
+ }
784
+ } else {
777
785
go func () {
786
+ defer wg .Done ()
778
787
s .handleStream (st , stream , s .traceInfo (st , stream ))
779
- wg .Done ()
780
788
}()
781
789
}
782
790
}, func (ctx context.Context , method string ) context.Context {
@@ -788,8 +796,10 @@ func (s *Server) serveStreams(st transport.ServerTransport) {
788
796
})
789
797
wg .Wait ()
790
798
791
- for _ , ch := range streamChannels {
792
- close (ch )
799
+ if s .opts .numStreamWorkers > 0 {
800
+ for _ , ch := range streamChannels {
801
+ close (ch )
802
+ }
793
803
}
794
804
}
795
805
0 commit comments