grpc/grpc-go · critical · ErrServerStopped

grpc: the server has been stopped

Error message

grpc: the server has been stopped

What it means

Sentinel error ErrServerStopped (server.go:856) returned by Server.Serve (and xds.GRPCServer.Serve at xds/server.go:183) when Serve() is invoked after the server has already been stopped via Stop(), GracefulStop(), or Close(). It signals an illegal lifecycle transition: a gRPC Server is single-use; once stopped its internal listener map (s.lis) is nilled and its quit event has fired, so it cannot accept connections again.

Solutions

  1. Do not reuse a stopped *grpc.Server; construct a fresh grpc.NewServer() / xds.NewGRPCServer() before calling Serve again.
  2. Guard Serve with a single-flight/once flag so Stop()/GracefulStop() and Serve cannot race, and so Serve is only called once per server instance.
  3. If you need live reload, keep the existing Server running and rebuild a new listener+server, swapping them atomically, instead of restarting the same Server.
  4. Check the returned error with errors.Is(err, grpc.ErrServerStopped) to distinguish this from listener errors and surface a clear lifecycle message.

Example fix

// before
srv := grpc.NewServer()
srv.Stop()
srv.Serve(lis) // returns grpc.ErrServerStopped

// after
srv := grpc.NewServer()
srv.Stop()
srv = grpc.NewServer() // build a fresh server
register(srv)
go srv.Serve(lis)
Defensive patterns

Strategy: validation

Validate before calling

// A grpc.Server is single-use. Validate lifecycle before calling Serve.
if serverStopped {
    srv = grpc.NewServer()
    registerHandlers(srv)
}
// Then:
if err := srv.Serve(lis); err != nil {
    if errors.Is(err, grpc.ErrServerStopped) {
        // lifecycle bug: Serve called after Stop/GracefulStop
    }
}

Try / catch

if err := srv.Serve(lis); err != nil {
    if errors.Is(err, grpc.ErrServerStopped) {
        // Build a new server instead of reusing the stopped one.
    } else {
        // ordinary Serve error (listener failure)
    }
}

Prevention

When it happens

Trigger: Calling server.Serve(lis) a second time, or calling Serve(lis2) on a different listener after Stop()/GracefulStop() returned; in xds, calling GRPCServer.Serve after GRPCServer.Close fired the quit event (checked via s.quit.HasFired() at xds/server.go:182).

Common situations: Restart loops that reuse the same *grpc.Server; HTTP/gRPC muxing where the server is stopped on SIGHUP then Serve is re-entered; tests that defer Stop() then call Serve again; xds.GRPCServer that was Close()d on error being reused.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/b10b7c6e4cd4f6af. Report an issue: GitHub.

Appendix: source

Thrown at server.go:856

		for m, d := range srv.streams {
			methods = append(methods, MethodInfo{
				Name:           m,
				IsClientStream: d.ClientStreams,
				IsServerStream: d.ServerStreams,
			})
		}

		ret[n] = ServiceInfo{
			Methods:  methods,
			Metadata: srv.mdata,
		}
	}
	return ret
}

// ErrServerStopped indicates that the operation is now illegal because of
// the server being stopped.
var ErrServerStopped = errors.New("grpc: the server has been stopped")

type listenSocket struct {
	net.Listener
	channelz *channelz.Socket
}

func (l *listenSocket) Close() error {
	err := l.Listener.Close()
	channelz.RemoveEntry(l.channelz.ID)
	channelz.Info(logger, l.channelz, "ListenSocket deleted")
	return err
}

// Serve accepts incoming connections on the listener lis, creating a new
// ServerTransport and service goroutine for each. The service goroutines
// read gRPC requests and then call the registered handlers to reply to them.
// Serve returns when lis.Accept fails with fatal errors.  lis will be closed when
// this method returns.

View on GitHub (pinned to 0c51461d27)