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  }