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 }