blob: aca420d27d09d7867ad8d43c95c07f7ae02cce70 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package health
import (
"context"
"sync"
)
import (
"github.com/dubbogo/gost/log/logger"
"github.com/dubbogo/grpc-go/codes"
"github.com/dubbogo/grpc-go/status"
)
import (
"dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/config"
"dubbo.apache.org/dubbo-go/v3/internal"
"dubbo.apache.org/dubbo-go/v3/protocol/triple/health/triple_health"
"dubbo.apache.org/dubbo-go/v3/server"
)
type HealthTripleServer struct {
mu sync.RWMutex
// If shutdown is true, it's expected all serving status is NOT_SERVING, and
// will stay in NOT_SERVING.
shutdown bool
// statusMap stores the serving status of the services this Server monitors.
statusMap map[string]triple_health.HealthCheckResponse_ServingStatus
updates map[string]map[triple_health.Health_WatchServer]chan triple_health.HealthCheckResponse_ServingStatus
}
var healthServer *HealthTripleServer
func NewServer() *HealthTripleServer {
return &HealthTripleServer{
statusMap: map[string]triple_health.HealthCheckResponse_ServingStatus{"": triple_health.HealthCheckResponse_NOT_SERVING},
updates: make(map[string]map[triple_health.Health_WatchServer]chan triple_health.HealthCheckResponse_ServingStatus),
}
}
func (srv *HealthTripleServer) Reference() string {
return constant.HealthCheckServiceTypeName
}
func (srv *HealthTripleServer) Check(ctx context.Context, req *triple_health.HealthCheckRequest) (*triple_health.HealthCheckResponse, error) {
srv.mu.RLock()
defer srv.mu.RUnlock()
if servingStatus, ok := srv.statusMap[req.Service]; ok {
return &triple_health.HealthCheckResponse{
Status: servingStatus,
}, nil
}
return nil, status.Error(codes.NotFound, "unknown service")
}
func (srv *HealthTripleServer) Watch(ctx context.Context, request *triple_health.HealthCheckRequest, server triple_health.Health_WatchServer) error {
service := request.Service
// update channel is used for getting service status updates.
update := make(chan triple_health.HealthCheckResponse_ServingStatus, 1)
srv.mu.Lock()
// Puts the initial status to the channel.
if servingStatus, ok := srv.statusMap[service]; ok {
update <- servingStatus
} else {
update <- triple_health.HealthCheckResponse_SERVICE_UNKNOWN
}
// Registers the update channel to the correct place in the updates map.
if _, ok := srv.updates[service]; !ok {
srv.updates[service] = make(map[triple_health.Health_WatchServer]chan triple_health.HealthCheckResponse_ServingStatus)
}
srv.updates[service][server] = update
defer func() {
srv.mu.Lock()
delete(srv.updates[service], server)
srv.mu.Unlock()
}()
srv.mu.Unlock()
var lastSentStatus triple_health.HealthCheckResponse_ServingStatus = -1
for {
select {
// Status updated. Sends the up-to-date status to the client.
case servingStatus := <-update:
if lastSentStatus == servingStatus {
continue
}
lastSentStatus = servingStatus
err := server.Send(&triple_health.HealthCheckResponse{Status: servingStatus})
if err != nil {
return status.Error(codes.Canceled, "Stream has ended.")
}
// Context done. Removes the update channel from the updates map.
case <-ctx.Done():
return status.Error(codes.Canceled, "Stream has ended.")
}
}
}
// SetServingStatus is called when need to reset the serving status of a service
// or insert a new service entry into the statusMap.
func (srv *HealthTripleServer) SetServingStatus(service string, servingStatus triple_health.HealthCheckResponse_ServingStatus) {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.shutdown {
logger.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus)
return
}
srv.setServingStatusLocked(service, servingStatus)
}
func (srv *HealthTripleServer) setServingStatusLocked(service string, servingStatus triple_health.HealthCheckResponse_ServingStatus) {
srv.statusMap[service] = servingStatus
for _, update := range srv.updates[service] {
// Clears previous updates, that are not sent to the client, from the channel.
// This can happen if the client is not reading and the server gets flow control limited.
select {
case <-update:
default:
}
// Puts the most recent update to the channel.
update <- servingStatus
}
}
// Shutdown sets all serving status to NOT_SERVING, and configures the server to
// ignore all future status changes.
//
// This changes serving status for all services. To set status for a particular
// services, call SetServingStatus().
func (srv *HealthTripleServer) Shutdown() {
srv.mu.Lock()
defer srv.mu.Unlock()
srv.shutdown = true
for service := range srv.statusMap {
srv.setServingStatusLocked(service, triple_health.HealthCheckResponse_NOT_SERVING)
}
}
// Resume sets all serving status to SERVING, and configures the server to
// accept all future status changes.
//
// This changes serving status for all services. To set status for a particular
// services, call SetServingStatus().
func (srv *HealthTripleServer) Resume() {
srv.mu.Lock()
defer srv.mu.Unlock()
srv.shutdown = false
for service := range srv.statusMap {
srv.setServingStatusLocked(service, triple_health.HealthCheckResponse_SERVING)
}
}
func init() {
healthServer = NewServer()
internal.HealthSetServingStatusServing = SetServingStatusServing
server.SetProServices(&server.ServiceDefinition{
Handler: healthServer,
Info: &triple_health.Health_ServiceInfo,
Opts: []server.ServiceOption{server.WithNotRegister(),
server.WithInterface(constant.HealthCheckServiceInterface)},
})
// In order to adapt config.Load
// Plans for future removal
config.SetProviderServiceWithInfo(healthServer, &triple_health.Health_ServiceInfo)
}
func SetServingStatusServing(service string) {
healthServer.SetServingStatus(service, triple_health.HealthCheckResponse_SERVING)
}
func SetServingStatusNotServing(service string) {
healthServer.SetServingStatus(service, triple_health.HealthCheckResponse_NOT_SERVING)
}