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