gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/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 "gitee.com/zhaochuninhefei/gmgo/grpc/codes" 28 healthgrpc "gitee.com/zhaochuninhefei/gmgo/grpc/health/grpc_health_v1" 29 healthpb "gitee.com/zhaochuninhefei/gmgo/grpc/health/grpc_health_v1" 30 "gitee.com/zhaochuninhefei/gmgo/grpc/status" 31 ) 32 33 // Server implements `service Health`. 34 type Server struct { 35 healthgrpc.UnimplementedHealthServer 36 mu sync.RWMutex 37 // If shutdown is true, it's expected all serving status is NOT_SERVING, and 38 // will stay in NOT_SERVING. 39 shutdown bool 40 // statusMap stores the serving status of the services this Server monitors. 41 statusMap map[string]healthpb.HealthCheckResponse_ServingStatus 42 updates map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus 43 } 44 45 // NewServer returns a new Server. 46 func NewServer() *Server { 47 return &Server{ 48 statusMap: map[string]healthpb.HealthCheckResponse_ServingStatus{"": healthpb.HealthCheckResponse_SERVING}, 49 updates: make(map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus), 50 } 51 } 52 53 // Check implements `service Health`. 54 //goland:noinspection GoUnusedParameter 55 func (s *Server) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { 56 s.mu.RLock() 57 defer s.mu.RUnlock() 58 if servingStatus, ok := s.statusMap[in.Service]; ok { 59 return &healthpb.HealthCheckResponse{ 60 Status: servingStatus, 61 }, nil 62 } 63 return nil, status.Error(codes.NotFound, "unknown service") 64 } 65 66 // Watch implements `service Health`. 67 func (s *Server) Watch(in *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error { 68 service := in.Service 69 // update channel is used for getting service status updates. 70 update := make(chan healthpb.HealthCheckResponse_ServingStatus, 1) 71 s.mu.Lock() 72 // Puts the initial status to the channel. 73 if servingStatus, ok := s.statusMap[service]; ok { 74 update <- servingStatus 75 } else { 76 update <- healthpb.HealthCheckResponse_SERVICE_UNKNOWN 77 } 78 79 // Registers the update channel to the correct place in the updates map. 80 if _, ok := s.updates[service]; !ok { 81 s.updates[service] = make(map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus) 82 } 83 s.updates[service][stream] = update 84 defer func() { 85 s.mu.Lock() 86 delete(s.updates[service], stream) 87 s.mu.Unlock() 88 }() 89 s.mu.Unlock() 90 91 var lastSentStatus healthpb.HealthCheckResponse_ServingStatus = -1 92 for { 93 select { 94 // Status updated. Sends the up-to-date status to the client. 95 case servingStatus := <-update: 96 if lastSentStatus == servingStatus { 97 continue 98 } 99 lastSentStatus = servingStatus 100 err := stream.Send(&healthpb.HealthCheckResponse{Status: servingStatus}) 101 if err != nil { 102 return status.Error(codes.Canceled, "Stream has ended.") 103 } 104 // Context done. Removes the update channel from the updates map. 105 case <-stream.Context().Done(): 106 return status.Error(codes.Canceled, "Stream has ended.") 107 } 108 } 109 } 110 111 // SetServingStatus is called when need to reset the serving status of a service 112 // or insert a new service entry into the statusMap. 113 func (s *Server) SetServingStatus(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 114 s.mu.Lock() 115 defer s.mu.Unlock() 116 if s.shutdown { 117 logger.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus) 118 return 119 } 120 121 s.setServingStatusLocked(service, servingStatus) 122 } 123 124 func (s *Server) setServingStatusLocked(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 125 s.statusMap[service] = servingStatus 126 for _, update := range s.updates[service] { 127 // Clears previous updates, that are not sent to the client, from the channel. 128 // This can happen if the client is not reading and the server gets flow control limited. 129 select { 130 case <-update: 131 default: 132 } 133 // Puts the most recent update to the channel. 134 update <- servingStatus 135 } 136 } 137 138 // Shutdown sets all serving status to NOT_SERVING, and configures the server to 139 // ignore all future status changes. 140 // 141 // This changes serving status for all services. To set status for a particular 142 // services, call SetServingStatus(). 143 func (s *Server) Shutdown() { 144 s.mu.Lock() 145 defer s.mu.Unlock() 146 s.shutdown = true 147 for service := range s.statusMap { 148 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_NOT_SERVING) 149 } 150 } 151 152 // Resume sets all serving status to SERVING, and configures the server to 153 // accept all future status changes. 154 // 155 // This changes serving status for all services. To set status for a particular 156 // services, call SetServingStatus(). 157 func (s *Server) Resume() { 158 s.mu.Lock() 159 defer s.mu.Unlock() 160 s.shutdown = false 161 for service := range s.statusMap { 162 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_SERVING) 163 } 164 }