dubbo.apache.org/dubbo-go/v3@v3.1.1/config/graceful_shutdown.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 config
    19  
    20  import (
    21  	"os"
    22  	"os/signal"
    23  	"runtime/debug"
    24  	"time"
    25  )
    26  
    27  import (
    28  	gxset "github.com/dubbogo/gost/container/set"
    29  	"github.com/dubbogo/gost/log/logger"
    30  )
    31  
    32  import (
    33  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    34  	"dubbo.apache.org/dubbo-go/v3/common/extension"
    35  )
    36  
    37  /*
    38   * The key point is that find out the signals to handle.
    39   * The most important documentation is https://golang.org/pkg/os/signal/
    40   * From this documentation, we can know that:
    41   * 1. The signals SIGKILL and SIGSTOP may not be caught by signal package;
    42   * 2. SIGHUP, SIGINT, or SIGTERM signal causes the program to exit
    43   * 3. SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, or SIGSYS signal causes the program to exit with a stack dump
    44   * 4. The invocation of Notify(signal...) will disable the default behavior of those signals.
    45   *
    46   * So the signals SIGKILL, SIGSTOP, SIGHUP, SIGINT, SIGTERM, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, SIGSYS
    47   * should be processed.
    48   * syscall.SIGEMT cannot be found in CI
    49   * It's seems that the Unix/Linux does not have the signal SIGSTKFLT. https://github.com/golang/go/issues/33381
    50   * So this signal will be ignored.
    51   * The signals are different on different platforms.
    52   * We define them by using 'package build' feature https://golang.org/pkg/go/build/
    53   */
    54  const defaultShutDownTime = time.Second * 60
    55  
    56  func gracefulShutdownInit() {
    57  	// retrieve ShutdownConfig for gracefulShutdownFilter
    58  	cGracefulShutdownFilter, existcGracefulShutdownFilter := extension.GetFilter(constant.GracefulShutdownConsumerFilterKey)
    59  	if !existcGracefulShutdownFilter {
    60  		return
    61  	}
    62  	sGracefulShutdownFilter, existsGracefulShutdownFilter := extension.GetFilter(constant.GracefulShutdownProviderFilterKey)
    63  	if !existsGracefulShutdownFilter {
    64  		return
    65  	}
    66  	if filter, ok := cGracefulShutdownFilter.(Setter); ok && rootConfig.Shutdown != nil {
    67  		filter.Set(constant.GracefulShutdownFilterShutdownConfig, GetShutDown())
    68  	}
    69  
    70  	if filter, ok := sGracefulShutdownFilter.(Setter); ok && rootConfig.Shutdown != nil {
    71  		filter.Set(constant.GracefulShutdownFilterShutdownConfig, GetShutDown())
    72  	}
    73  
    74  	if GetShutDown().GetInternalSignal() {
    75  		signals := make(chan os.Signal, 1)
    76  		signal.Notify(signals, ShutdownSignals...)
    77  
    78  		go func() {
    79  			select {
    80  			case sig := <-signals:
    81  				logger.Infof("get signal %s, applicationConfig will shutdown.", sig)
    82  				// gracefulShutdownOnce.Do(func() {
    83  				time.AfterFunc(totalTimeout(), func() {
    84  					logger.Warn("Shutdown gracefully timeout, applicationConfig will shutdown immediately. ")
    85  					os.Exit(0)
    86  				})
    87  				BeforeShutdown()
    88  				// those signals' original behavior is exit with dump ths stack, so we try to keep the behavior
    89  				for _, dumpSignal := range DumpHeapShutdownSignals {
    90  					if sig == dumpSignal {
    91  						debug.WriteHeapDump(os.Stdout.Fd())
    92  					}
    93  				}
    94  				os.Exit(0)
    95  			}
    96  		}()
    97  	}
    98  }
    99  
   100  // BeforeShutdown provides processing flow before shutdown
   101  func BeforeShutdown() {
   102  	destroyAllRegistries()
   103  	// waiting for a short time so that the clients have enough time to get the notification that server shutdowns
   104  	// The value of configuration depends on how long the clients will get notification.
   105  	waitAndAcceptNewRequests()
   106  
   107  	// reject sending/receiving the new request, but keeping waiting for accepting requests
   108  	waitForSendingAndReceivingRequests()
   109  
   110  	// destroy all protocols
   111  	destroyProtocols()
   112  
   113  	logger.Info("Graceful shutdown --- Execute the custom callbacks.")
   114  	customCallbacks := extension.GetAllCustomShutdownCallbacks()
   115  	for callback := customCallbacks.Front(); callback != nil; callback = callback.Next() {
   116  		callback.Value.(func())()
   117  	}
   118  }
   119  
   120  func destroyAllRegistries() {
   121  	logger.Info("Graceful shutdown --- Destroy all registriesConfig. ")
   122  	registryProtocol := extension.GetProtocol(constant.RegistryProtocol)
   123  	registryProtocol.Destroy()
   124  }
   125  
   126  // destroyProtocols destroys protocols.
   127  // First we destroy provider's protocols, and then we destroy the consumer protocols.
   128  func destroyProtocols() {
   129  	logger.Info("Graceful shutdown --- Destroy protocols. ")
   130  
   131  	if rootConfig.Protocols == nil {
   132  		return
   133  	}
   134  
   135  	consumerProtocols := getConsumerProtocols()
   136  
   137  	destroyProviderProtocols(consumerProtocols)
   138  	destroyConsumerProtocols(consumerProtocols)
   139  }
   140  
   141  // destroyProviderProtocols destroys the provider's protocol.
   142  // if the protocol is consumer's protocol too, we will keep it
   143  func destroyProviderProtocols(consumerProtocols *gxset.HashSet) {
   144  	logger.Info("Graceful shutdown --- First destroy provider's protocols. ")
   145  	for _, protocol := range rootConfig.Protocols {
   146  		// the protocol is the consumer's protocol too, we can not destroy it.
   147  		if consumerProtocols.Contains(protocol.Name) {
   148  			continue
   149  		}
   150  		extension.GetProtocol(protocol.Name).Destroy()
   151  	}
   152  }
   153  
   154  func destroyConsumerProtocols(consumerProtocols *gxset.HashSet) {
   155  	logger.Info("Graceful shutdown --- Second Destroy consumer's protocols. ")
   156  	for name := range consumerProtocols.Items {
   157  		extension.GetProtocol(name.(string)).Destroy()
   158  	}
   159  }
   160  
   161  func waitAndAcceptNewRequests() {
   162  	logger.Info("Graceful shutdown --- Keep waiting and accept new requests for a short time. ")
   163  	if rootConfig.Shutdown == nil {
   164  		return
   165  	}
   166  
   167  	time.Sleep(rootConfig.Shutdown.GetConsumerUpdateWaitTime())
   168  
   169  	timeout := rootConfig.Shutdown.GetStepTimeout()
   170  	// ignore this step
   171  	if timeout < 0 {
   172  		return
   173  	}
   174  	waitingProviderProcessedTimeout(rootConfig.Shutdown)
   175  }
   176  
   177  func waitingProviderProcessedTimeout(shutdownConfig *ShutdownConfig) {
   178  	timeout := shutdownConfig.GetStepTimeout()
   179  	if timeout <= 0 {
   180  		return
   181  	}
   182  	deadline := time.Now().Add(timeout)
   183  
   184  	offlineRequestWindowTimeout := shutdownConfig.GetOfflineRequestWindowTimeout()
   185  	for time.Now().Before(deadline) &&
   186  		(shutdownConfig.ProviderActiveCount.Load() > 0 || time.Now().Before(shutdownConfig.ProviderLastReceivedRequestTime.Load().Add(offlineRequestWindowTimeout))) {
   187  		// sleep 10 ms and then we check it again
   188  		time.Sleep(10 * time.Millisecond)
   189  		logger.Infof("waiting for provider active invocation count = %d, provider last received request time: %v",
   190  			shutdownConfig.ProviderActiveCount.Load(), shutdownConfig.ProviderLastReceivedRequestTime.Load())
   191  	}
   192  }
   193  
   194  // for provider. It will wait for processing receiving requests
   195  func waitForSendingAndReceivingRequests() {
   196  	logger.Info("Graceful shutdown --- Keep waiting until sending/accepting requests finish or timeout. ")
   197  	if rootConfig == nil || rootConfig.Shutdown == nil {
   198  		// ignore this step
   199  		return
   200  	}
   201  	rootConfig.Shutdown.RejectRequest.Store(true)
   202  	waitingConsumerProcessedTimeout(rootConfig.Shutdown)
   203  }
   204  
   205  func waitingConsumerProcessedTimeout(shutdownConfig *ShutdownConfig) {
   206  	timeout := shutdownConfig.GetStepTimeout()
   207  	if timeout <= 0 {
   208  		return
   209  	}
   210  	deadline := time.Now().Add(timeout)
   211  
   212  	for time.Now().Before(deadline) && shutdownConfig.ConsumerActiveCount.Load() > 0 {
   213  		// sleep 10 ms and then we check it again
   214  		time.Sleep(10 * time.Millisecond)
   215  		logger.Infof("waiting for consumer active invocation count = %d", shutdownConfig.ConsumerActiveCount.Load())
   216  	}
   217  }
   218  
   219  func totalTimeout() time.Duration {
   220  	timeout := defaultShutDownTime
   221  	if rootConfig.Shutdown != nil && rootConfig.Shutdown.GetTimeout() > timeout {
   222  		timeout = rootConfig.Shutdown.GetTimeout()
   223  	}
   224  
   225  	return timeout
   226  }
   227  
   228  // we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc.
   229  func getConsumerProtocols() *gxset.HashSet {
   230  	result := gxset.NewSet()
   231  	if rootConfig.Consumer == nil || rootConfig.Consumer.References == nil {
   232  		return result
   233  	}
   234  
   235  	for _, reference := range rootConfig.Consumer.References {
   236  		result.Add(reference.Protocol)
   237  	}
   238  	return result
   239  }