server: fix Serve() vs. immediate Shutdown() race.

Fix a race where an asynchronous server.Serve() invoked in a
a goroutine races with an almost immediate server.Shutdown().
If Shutdown() finishes its locked closing of listeners before
Serve() gets around to add the new one, Serve will sit stuck
forever in l.Accept(), unless the caller closes the listener
in addition to Shutdown().

This is probably almost impossible to trigger in real life,
but some of the unit tests, which run the server and client
in the same process, occasionally do trigger this. Then, if
the test tries to verify a final ErrServerClosed error from
Serve() after Shutdown() it gets stuck forever.

Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
This commit is contained in:
Krisztian Litkey
2024-09-13 15:06:23 +03:00
parent ed6c3ba082
commit c4d96d55ad

View File

@@ -74,9 +74,18 @@ func (s *Server) RegisterService(name string, desc *ServiceDesc) {
} }
func (s *Server) Serve(ctx context.Context, l net.Listener) error { func (s *Server) Serve(ctx context.Context, l net.Listener) error {
s.addListener(l) s.mu.Lock()
s.addListenerLocked(l)
defer s.closeListener(l) defer s.closeListener(l)
select {
case <-s.done:
s.mu.Unlock()
return ErrServerClosed
default:
}
s.mu.Unlock()
var ( var (
backoff time.Duration backoff time.Duration
handshaker = s.config.handshaker handshaker = s.config.handshaker
@@ -188,9 +197,7 @@ func (s *Server) Close() error {
return err return err
} }
func (s *Server) addListener(l net.Listener) { func (s *Server) addListenerLocked(l net.Listener) {
s.mu.Lock()
defer s.mu.Unlock()
s.listeners[l] = struct{}{} s.listeners[l] = struct{}{}
} }