#9393·grpc-go

在 GracefulStop() 之后调用 Stop() 时,会导致死锁,因为处理程序仍在运行

作者: stanhu创建于 2026年9月2日更新于 2026年9月15日
标签P2Type: Bug

Use case(s) / How is this problem addressed in other systems? The recommended pattern for a bounded graceful shutdown is: call GracefulStop(), and if it does not finish within a deadline, call Stop() to force-terminate the remaining RPCs (the SIGTERM-then-SIGKILL model). This was also discussed in #8480. However, calling Stop() after (or concurrently with) GracefulStop() can deadlock the server mutex and block until the handler returns on its own. In production, we observed 15-minute delays. ### What version of gRPC are you using? v1.83.1 ### What did you do? Run GracefulStop() in a goroutine, let the client connections drain, then call Stop(), while one RPC handler is still running and does not return promptly on context cancellation. Minimal reproduction: ```go // Standalone reproduction of the gRPC-go GracefulStop/Stop mutex deadlock. // go mod init grpcrepro // go get google.golang.org/[email protected] // go run . // Expected (buggy) output: "REPRODUCED: Stop() is still blocked after 15s". package main import ("context" "log" "net" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" healthpb "google.golang.org/grpc/health/grpc_health_v1") type blockingHealth struct { healthpb.UnimplementedHealthServer started chan struct{} release chan struct{} } func (h *blockingHealth) Check(context.Context, *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { close(h.started) <-h.release return &healthpb.HealthCheckResponse{}, nil } func main() { s := grpc.NewServer(grpc.WaitForHandlers(false)) h := &blockingHealth{started: make(chan struct{}), release: make(chan struct{})} healthpb.RegisterHealthServer(s, h) lis, err := net.Listen("tcp", "localhost:0") if err != nil { log.Fatal(err) } go func() { _ = s.Serve(lis) }() conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal(err) } go func() { _, _ = healthpb.NewHealthClient(conn).Check(context.Background(), &healthpb.HealthCheckRequest{}) }() <-h.started // handler is running and counted in handlersWG log.Println("handler running; starting GracefulStop()") go s.GracefulStop() // The client connection drains during the "grace period"; GracefulStop then advances past its conns loop into handlersWG.Wait(), holding the server mutex. time.Sleep(200 * time.Millisecond) _ = conn.Close() time.Sleep(time.Second) log.Println("grace period elapsed; calling Stop()") done := make(chan struct{}) start := time.Now() go func() { s.Stop(); close(done) }() select { case <-done: log.Printf("NOT reproduced: Stop() returned in %s", time.Since(start)) case <-time.After(15 * time.Second): log.Printf("REPRODUCED: Stop() is still blocked after %s (deadlocked on the server mutex)", time.Since(start)) } }