dubbo.apache.org/dubbo-go/v3@v3.1.1/protocol/dubbo3/health/serverhealth.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. 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 // Package health provides a service that exposes server's health and it must be 19 // imported to enable support for client-side health checks. 20 package health 21 22 import ( 23 "context" 24 "sync" 25 ) 26 27 import ( 28 "github.com/dubbogo/gost/log/logger" 29 30 "github.com/dubbogo/grpc-go/codes" 31 "github.com/dubbogo/grpc-go/status" 32 ) 33 34 import ( 35 "dubbo.apache.org/dubbo-go/v3/config" 36 healthpb "dubbo.apache.org/dubbo-go/v3/protocol/dubbo3/health/triple_health_v1" 37 ) 38 39 // Server implements `service Health`. 40 type DubbogoHealthServer struct { 41 healthpb.UnimplementedHealthServer 42 mu sync.RWMutex 43 // If shutdown is true, it's expected all serving status is NOT_SERVING, and 44 // will stay in NOT_SERVING. 45 shutdown bool 46 // statusMap stores the serving status of the services this Server monitors. 47 statusMap map[string]healthpb.HealthCheckResponse_ServingStatus 48 updates map[string]map[healthpb.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus 49 } 50 51 var healthServer *DubbogoHealthServer 52 53 // NewServer returns a new Server. 54 func NewServer() *DubbogoHealthServer { 55 return &DubbogoHealthServer{ 56 statusMap: map[string]healthpb.HealthCheckResponse_ServingStatus{"": healthpb.HealthCheckResponse_SERVING}, 57 updates: make(map[string]map[healthpb.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus), 58 } 59 } 60 61 // Check implements `service Health`. 62 func (s *DubbogoHealthServer) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { 63 s.mu.RLock() 64 defer s.mu.RUnlock() 65 if servingStatus, ok := s.statusMap[in.Service]; ok { 66 return &healthpb.HealthCheckResponse{ 67 Status: servingStatus, 68 }, nil 69 } 70 return nil, status.Error(codes.NotFound, "unknown service") 71 } 72 73 // Watch implements `service Health`. 74 func (s *DubbogoHealthServer) Watch(in *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { 75 service := in.Service 76 // update channel is used for getting service status updates. 77 update := make(chan healthpb.HealthCheckResponse_ServingStatus, 1) 78 s.mu.Lock() 79 // Puts the initial status to the channel. 80 if servingStatus, ok := s.statusMap[service]; ok { 81 update <- servingStatus 82 } else { 83 update <- healthpb.HealthCheckResponse_SERVICE_UNKNOWN 84 } 85 86 // Registers the update channel to the correct place in the updates map. 87 if _, ok := s.updates[service]; !ok { 88 s.updates[service] = make(map[healthpb.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus) 89 } 90 s.updates[service][stream] = update 91 defer func() { 92 s.mu.Lock() 93 delete(s.updates[service], stream) 94 s.mu.Unlock() 95 }() 96 s.mu.Unlock() 97 98 var lastSentStatus healthpb.HealthCheckResponse_ServingStatus = -1 99 for { 100 select { 101 // Status updated. Sends the up-to-date status to the client. 102 case servingStatus := <-update: 103 if lastSentStatus == servingStatus { 104 continue 105 } 106 lastSentStatus = servingStatus 107 err := stream.Send(&healthpb.HealthCheckResponse{Status: servingStatus}) 108 if err != nil { 109 return status.Error(codes.Canceled, "Stream has ended.") 110 } 111 // Context done. Removes the update channel from the updates map. 112 case <-stream.Context().Done(): 113 return status.Error(codes.Canceled, "Stream has ended.") 114 } 115 } 116 } 117 118 // SetServingStatus is called when need to reset the serving status of a service 119 // or insert a new service entry into the statusMap. 120 func (s *DubbogoHealthServer) SetServingStatus(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 121 s.mu.Lock() 122 defer s.mu.Unlock() 123 if s.shutdown { 124 logger.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus) 125 return 126 } 127 128 s.setServingStatusLocked(service, servingStatus) 129 } 130 131 func (s *DubbogoHealthServer) setServingStatusLocked(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) { 132 s.statusMap[service] = servingStatus 133 for _, update := range s.updates[service] { 134 // Clears previous updates, that are not sent to the client, from the channel. 135 // This can happen if the client is not reading and the server gets flow control limited. 136 select { 137 case <-update: 138 default: 139 } 140 // Puts the most recent update to the channel. 141 update <- servingStatus 142 } 143 } 144 145 // Shutdown sets all serving status to NOT_SERVING, and configures the server to 146 // ignore all future status changes. 147 // 148 // This changes serving status for all services. To set status for a particular 149 // services, call SetServingStatus(). 150 func (s *DubbogoHealthServer) Shutdown() { 151 s.mu.Lock() 152 defer s.mu.Unlock() 153 s.shutdown = true 154 for service := range s.statusMap { 155 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_NOT_SERVING) 156 } 157 } 158 159 // Resume sets all serving status to SERVING, and configures the server to 160 // accept all future status changes. 161 // 162 // This changes serving status for all services. To set status for a particular 163 // services, call SetServingStatus(). 164 func (s *DubbogoHealthServer) Resume() { 165 s.mu.Lock() 166 defer s.mu.Unlock() 167 s.shutdown = false 168 for service := range s.statusMap { 169 s.setServingStatusLocked(service, healthpb.HealthCheckResponse_SERVING) 170 } 171 } 172 173 // Set health check interface. 174 func init() { 175 healthServer = NewServer() 176 config.SetProviderService(healthServer) 177 } 178 179 func SetServingStatusServing(service string) { 180 healthServer.SetServingStatus(service, healthpb.HealthCheckResponse_SERVING) 181 } 182 183 func SetServingStatusNotServing(service string) { 184 healthServer.SetServingStatus(service, healthpb.HealthCheckResponse_NOT_SERVING) 185 }