/* * * Copyright 2024 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Binary server demonstrates how to gracefully stop a gRPC server. package main import ( "context" "errors" "flag" "fmt" "io" "log" "net" "sync/atomic" "time" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/features/proto/echo" ) var ( port = flag.Int("port", 50052, "port number") ) type server struct { pb.UnimplementedEchoServer unaryRequests atomic.Int32 // to track number of unary RPCs processed streamStart chan struct{} // to signal if server streaming started } // ClientStreamingEcho implements the EchoService.ClientStreamingEcho method. // It signals the server that streaming has started and waits for the stream to // be done or aborted. If `io.EOF` is received on stream that means client // has successfully closed the stream using `stream.CloseAndRecv()`, so it // returns an `EchoResponse` with the total number of unary RPCs processed // otherwise, it returns the error indicating stream is aborted. func (s *server) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error { // Signal streaming start to initiate graceful stop which should wait until // server streaming finishes. s.streamStart <- struct{}{} if err := stream.RecvMsg(&pb.EchoResponse{}); err != nil { if errors.Is(err, io.EOF) { stream.SendAndClose(&pb.EchoResponse{Message: fmt.Sprintf("%d", s.unaryRequests.Load())}) return nil } return err } return nil } // UnaryEcho implements the EchoService.UnaryEcho method. It increments // `s.unaryRequests` on every call and returns it as part of `EchoResponse`. func (s *server) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { s.unaryRequests.Add(1) return &pb.EchoResponse{Message: req.Message}, nil } func main() { flag.Parse() address := fmt.Sprintf(":%v", *port) lis, err := net.Listen("tcp", address) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() ss := &server{streamStart: make(chan struct{})} pb.RegisterEchoServer(s, ss) serverStopped := make(chan struct{}, 1) go func() { <-ss.streamStart // wait until server streaming starts time.Sleep(1 * time.Second) log.Println("Initiating graceful shutdown...") timer := time.AfterFunc(10*time.Second, func() { log.Println("Server couldn't stop gracefully in time. Doing force stop.") s.Stop() // Unblock the main function. select { case serverStopped <- struct{}{}: default: } }) defer timer.Stop() s.GracefulStop() // gracefully stop server after in-flight server streaming rpc finishes log.Println("Server stopped gracefully.") // Unblock the main function. select { case serverStopped <- struct{}{}: default: } }() if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } <-serverStopped }