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  }