google.golang.org/grpc@v1.74.2/health/server.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package health provides a service that exposes server's health and it must be 20 // imported to enable support for client-side health checks. 21 package health 22 23 import ( 24 "context" 25 "sync" 26 27 "google.golang.org/grpc/codes" 28 healthgrpc "google.golang.org/grpc/health/grpc_health_v1" 29 healthpb "google.golang.org/grpc/health/grpc_health_v1" 30 "google.golang.org/grpc/status" 31 ) 32 33 const ( 34 // maxAllowedServices defines the maximum number of resources a List 35 // operation can return. An error is returned if the number of services 36 // exceeds this limit. 37 maxAllowedServices = 100 38 ) 39 40 // Server implements `service Health`. 41 type Server struct { 42 healthgrpc.UnimplementedHealthServer 43 mu sync.RWMutex 44 // If shutdown is true, it's expected all serving status is NOT_SERVING, and 45 // will stay in NOT_SERVING. 46 shutdown bool 47 // statusMap stores the serving status of the services this Server monitors. 48 statusMap map[string]healthpb.HealthCheckResponse_ServingStatus 49 updates map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus 50 } 51 52 // NewServer returns a new Server. 53 func NewServer() *Server { 54 return &Server{ 55 statusMap: map[string]healthpb.HealthCheckResponse_ServingStatus{"": healthpb.HealthCheckResponse_SERVING}, 56 updates: make(map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus), 57 } 58 } 59 60 // Check implements `service Health`. 61 func (s *Server) Check(_ context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { 62 s.mu.RLock() 63 defer s.mu.RUnlock() 64 if servingStatus, ok := s.statusMap[in.Service]; ok { 65 return &healthpb.HealthCheckResponse{ 66 Status: servingStatus, 67 }, nil 68 } 69 return nil, status.Error(codes.NotFound, "unknown service") 70 } 71 72 // List implements `service Health`. 73 func (s *Server) List(_ context.Context, _ *healthpb.HealthListRequest) (*healthpb.HealthListResponse, error) { 74 s.mu.RLock() 75 defer s.mu.RUnlock() 76 77 if len(s.statusMap) > maxAllowedServices { 78 return nil, status.Errorf(codes.ResourceExhausted, "server health list exceeds maximum capacity: %d", maxAllowedServices) 79 } 80 81 statusMap := make(map[string]*healthpb.HealthCheckResponse, len(s.statusMap)) 82 for k, v := range s.statusMap { 83 statusMap[k] = &healthpb.HealthCheckResponse{Status: v} 84 } 85 86 return &healthpb.HealthListResponse{Statuses: statusMap}, nil 87 } 88 89 // Watch implements `service Health`. 90 func (s *Server) Watch(in *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error { 91 service := in.Service 92 // update channel is used for getting service status updates. 93 update := make(chan healthpb.HealthCheckResponse_ServingStatus, 1) 94 s.mu.Lock() 95 // Puts the initial status to the channel. 96 if servingStatus, ok := s.statusMap[service]; ok { 97 update <- servingStatus 98 } else { 99 update <- healthpb.HealthCheckResponse_SERVICE_UNKNOWN 100 } 101 102 // Registers the update channel to the correct place in the updates map. 103 if _, ok := s.updates[service]; !ok { 104 s.updates[service] = make(map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus) 105 } 106 s.updates[service][stream] = update 107 defer func() { 108 s.mu.Lock() 109 delete(s.updates[service], stream) 110 s.mu.Unlock() 111 }() 112 s.mu.Unlock() 113 114 var lastSentStatus healthpb.HealthCheckResponse_ServingStatus = -1 115 for { 116 select { 117 // Status updated. Sends the up-to-date status to the client. 118 case servingStatus := <-update: 119 if lastSentStatus == servingStatus { 120 continue 121 } 122 lastSentStatus = servingStatus 123 err := stream.Send(&healthpb.HealthCheckResponse{Status: servingStatus}) 124 if err != nil { 125 return status.Error(codes.Canceled, "Stream has ended.") 126 } 127 // Context done. Removes the update channel from the updates map. 128 case <-stream.Context().Done(): 129 return status.Error(codes.Canceled, "Stream has ended.") 130 } 131 } 132 } 133 134 // SetServingStatus is called when need to reset the serving status of a service 135 // or insert a new service entry into the statusMap. 136 func (s *Server) SetServingStatus(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 137 s.mu.Lock() 138 defer s.mu.Unlock() 139 if s.shutdown { 140 logger.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus) 141 return 142 } 143 144 s.setServingStatusLocked(service, servingStatus) 145 } 146 147 func (s *Server) setServingStatusLocked(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 148 s.statusMap[service] = servingStatus 149 for _, update := range s.updates[service] { 150 // Clears previous updates, that are not sent to the client, from the channel. 151 // This can happen if the client is not reading and the server gets flow control limited. 152 select { 153 case <-update: 154 default: 155 } 156 // Puts the most recent update to the channel. 157 update <- servingStatus 158 } 159 } 160 161 // Shutdown sets all serving status to NOT_SERVING, and configures the server to 162 // ignore all future status changes. 163 // 164 // This changes serving status for all services. To set status for a particular 165 // services, call SetServingStatus(). 166 func (s *Server) Shutdown() { 167 s.mu.Lock() 168 defer s.mu.Unlock() 169 s.shutdown = true 170 for service := range s.statusMap { 171 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_NOT_SERVING) 172 } 173 } 174 175 // Resume sets all serving status to SERVING, and configures the server to 176 // accept all future status changes. 177 // 178 // This changes serving status for all services. To set status for a particular 179 // services, call SetServingStatus(). 180 func (s *Server) Resume() { 181 s.mu.Lock() 182 defer s.mu.Unlock() 183 s.shutdown = false 184 for service := range s.statusMap { 185 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_SERVING) 186 } 187 }