blob: c86e4998897d1256e1bdadb716275a52f925d8b9 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001/*
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//go:generate ./regenerate.sh
20
21// Package health provides a service that exposes server's health and it must be
22// imported to enable support for client-side health checks.
23package health
24
25import (
26 "context"
27 "sync"
28
29 "google.golang.org/grpc/codes"
30 healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
31 healthpb "google.golang.org/grpc/health/grpc_health_v1"
32 "google.golang.org/grpc/status"
33)
34
35// Server implements `service Health`.
36type Server struct {
37 mu sync.Mutex
38 // statusMap stores the serving status of the services this Server monitors.
39 statusMap map[string]healthpb.HealthCheckResponse_ServingStatus
40 updates map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus
41}
42
43// NewServer returns a new Server.
44func NewServer() *Server {
45 return &Server{
46 statusMap: map[string]healthpb.HealthCheckResponse_ServingStatus{"": healthpb.HealthCheckResponse_SERVING},
47 updates: make(map[string]map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus),
48 }
49}
50
51// Check implements `service Health`.
52func (s *Server) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
53 s.mu.Lock()
54 defer s.mu.Unlock()
55 if servingStatus, ok := s.statusMap[in.Service]; ok {
56 return &healthpb.HealthCheckResponse{
57 Status: servingStatus,
58 }, nil
59 }
60 return nil, status.Error(codes.NotFound, "unknown service")
61}
62
63// Watch implements `service Health`.
64func (s *Server) Watch(in *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error {
65 service := in.Service
66 // update channel is used for getting service status updates.
67 update := make(chan healthpb.HealthCheckResponse_ServingStatus, 1)
68 s.mu.Lock()
69 // Puts the initial status to the channel.
70 if servingStatus, ok := s.statusMap[service]; ok {
71 update <- servingStatus
72 } else {
73 update <- healthpb.HealthCheckResponse_SERVICE_UNKNOWN
74 }
75
76 // Registers the update channel to the correct place in the updates map.
77 if _, ok := s.updates[service]; !ok {
78 s.updates[service] = make(map[healthgrpc.Health_WatchServer]chan healthpb.HealthCheckResponse_ServingStatus)
79 }
80 s.updates[service][stream] = update
81 defer func() {
82 s.mu.Lock()
83 delete(s.updates[service], stream)
84 s.mu.Unlock()
85 }()
86 s.mu.Unlock()
87
88 var lastSentStatus healthpb.HealthCheckResponse_ServingStatus = -1
89 for {
90 select {
91 // Status updated. Sends the up-to-date status to the client.
92 case servingStatus := <-update:
93 if lastSentStatus == servingStatus {
94 continue
95 }
96 lastSentStatus = servingStatus
97 err := stream.Send(&healthpb.HealthCheckResponse{Status: servingStatus})
98 if err != nil {
99 return status.Error(codes.Canceled, "Stream has ended.")
100 }
101 // Context done. Removes the update channel from the updates map.
102 case <-stream.Context().Done():
103 return status.Error(codes.Canceled, "Stream has ended.")
104 }
105 }
106}
107
108// SetServingStatus is called when need to reset the serving status of a service
109// or insert a new service entry into the statusMap.
110func (s *Server) SetServingStatus(service string, servingStatus healthpb.HealthCheckResponse_ServingStatus) {
111 s.mu.Lock()
112 defer s.mu.Unlock()
113
114 s.statusMap[service] = servingStatus
115 for _, update := range s.updates[service] {
116 // Clears previous updates, that are not sent to the client, from the channel.
117 // This can happen if the client is not reading and the server gets flow control limited.
118 select {
119 case <-update:
120 default:
121 }
122 // Puts the most recent update to the channel.
123 update <- servingStatus
124 }
125}