github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/utilcomponent/periodicalhandler/periodical_handler.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package periodicalhandler 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "runtime" 24 "sync" 25 "time" 26 27 "k8s.io/apimachinery/pkg/util/wait" 28 29 "github.com/kubewharf/katalyst-core/cmd/katalyst-agent/app/agent" 30 "github.com/kubewharf/katalyst-core/pkg/config" 31 dynamicconfig "github.com/kubewharf/katalyst-core/pkg/config/agent/dynamic" 32 "github.com/kubewharf/katalyst-core/pkg/metaserver" 33 "github.com/kubewharf/katalyst-core/pkg/metrics" 34 "github.com/kubewharf/katalyst-core/pkg/util/general" 35 ) 36 37 type Handler func(coreConf *config.Configuration, 38 extraConf interface{}, 39 dynamicConf *dynamicconfig.DynamicAgentConfiguration, 40 emitter metrics.MetricEmitter, 41 metaServer *metaserver.MetaServer) 42 43 type HandlerCtx struct { 44 handler Handler 45 ctx context.Context 46 cancel context.CancelFunc 47 interval time.Duration 48 ready bool 49 funcName string 50 } 51 52 // PeriodicalHandlerManager works as a general framework to run periodical jobs; 53 // you can register those jobs and mark them as started or stopped, and the manager 54 // will periodically check and run/cancel according to the job expected status. 55 type PeriodicalHandlerManager struct { 56 coreConf *config.Configuration 57 extraConf interface{} 58 dynamicConf *dynamicconfig.DynamicAgentConfiguration 59 emitter metrics.MetricEmitter 60 metaServer *metaserver.MetaServer 61 } 62 63 func NewPeriodicalHandlerManager(agentCtx *agent.GenericContext, coreConf *config.Configuration, 64 extraConf interface{}, agentName string, 65 ) (bool, agent.Component, error) { 66 wrappedEmitter := agentCtx.EmitterPool.GetDefaultMetricsEmitter().WithTags(agentName) 67 68 return true, &PeriodicalHandlerManager{ 69 coreConf: coreConf, 70 extraConf: extraConf, 71 emitter: wrappedEmitter, 72 metaServer: agentCtx.MetaServer, 73 dynamicConf: coreConf.DynamicAgentConfiguration, 74 }, nil 75 } 76 77 func (phm *PeriodicalHandlerManager) Run(ctx context.Context) { 78 wait.Until(func() { 79 handlerMtx.Lock() 80 defer handlerMtx.Unlock() 81 82 for groupName, groupHandlerCtxs := range handlerCtxs { 83 for handlerName := range groupHandlerCtxs { 84 handlerCtx := groupHandlerCtxs[handlerName] 85 if handlerCtx == nil { 86 general.Warningf("nil handlerCtx") 87 continue 88 } else if handlerCtx.ctx != nil { 89 continue 90 } else if !handlerCtx.ready { 91 general.InfoS("handler isn't ready", 92 "groupName", groupName, 93 "handlerName", handlerName, 94 "funcName", handlerCtx.funcName, 95 "interval", handlerCtx.interval) 96 continue 97 } 98 99 general.InfoS("start handler", 100 "groupName", groupName, 101 "handlerName", handlerName, 102 "funcName", handlerCtx.funcName, 103 "interval", handlerCtx.interval) 104 105 handlerCtx.ctx, handlerCtx.cancel = context.WithCancel(context.Background()) 106 go wait.Until(func() { 107 handlerCtx.handler(phm.coreConf, phm.extraConf, phm.dynamicConf, phm.emitter, phm.metaServer) 108 }, handlerCtx.interval, handlerCtx.ctx.Done()) 109 } 110 } 111 }, 5*time.Second, ctx.Done()) 112 } 113 114 // the first key is the handlers group name 115 // the second key is the handler name 116 var ( 117 handlerCtxs = make(map[string]map[string]*HandlerCtx) 118 handlerMtx sync.Mutex 119 ) 120 121 func RegisterPeriodicalHandlerWithHealthz(handlerName string, initState general.HealthzCheckState, groupName string, 122 handler Handler, interval time.Duration, tolerationTimes int64, 123 ) (err error) { 124 general.RegisterHeartbeatCheck(handlerName, time.Duration(tolerationTimes)*interval, initState, time.Duration(tolerationTimes)*interval) 125 return RegisterPeriodicalHandler(groupName, handlerName, handler, interval) 126 } 127 128 func RegisterPeriodicalHandler(groupName, handlerName string, handler Handler, interval time.Duration) (err error) { 129 if groupName == "" || handlerName == "" { 130 return fmt.Errorf("emptry groupName: %s or handlerName: %s", groupName, handlerName) 131 } else if handler == nil { 132 return fmt.Errorf("nil handler") 133 } else if interval <= 0 { 134 return fmt.Errorf("invalid interval: %v", interval) 135 } 136 137 defer func() { 138 if r := recover(); r != nil { 139 err = fmt.Errorf("recover from: %v", r) 140 return 141 } 142 }() 143 144 newFuncName := runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() 145 146 handlerMtx.Lock() 147 defer handlerMtx.Unlock() 148 149 if handlerCtxs[groupName][handlerName] != nil { 150 general.InfoS("replace periodical handler", 151 "groupName", groupName, 152 "handlerName", handlerName, 153 "oldFuncName", handlerCtxs[groupName][handlerName].funcName, 154 "oldInterval", handlerCtxs[groupName][handlerName].interval, 155 "newFuncName", newFuncName, 156 "newInterval", interval) 157 158 if handlerCtxs[groupName][handlerName].cancel != nil { 159 handlerCtxs[groupName][handlerName].cancel() 160 } 161 } else { 162 general.InfoS("add periodical handler", 163 "groupName", groupName, 164 "handlerName", handlerName, 165 "newFuncName", newFuncName, 166 "newInterval", interval) 167 168 if handlerCtxs[groupName] == nil { 169 handlerCtxs[groupName] = make(map[string]*HandlerCtx) 170 } 171 } 172 173 handlerCtxs[groupName][handlerName] = &HandlerCtx{ 174 handler: handler, 175 interval: interval, 176 funcName: newFuncName, 177 } 178 179 return nil 180 } 181 182 func ReadyToStartHandlersByGroup(groupName string) { 183 handlerMtx.Lock() 184 defer handlerMtx.Unlock() 185 186 general.InfoS("called", "groupName", groupName) 187 188 for handlerName, handlerCtx := range handlerCtxs[groupName] { 189 if handlerCtx == nil { 190 general.Warningf("nil handlerCtx") 191 continue 192 } else if handlerCtx.ctx != nil { 193 general.InfoS("handler already started", 194 "groupName", groupName, 195 "handlerName", handlerName, 196 "funcName", handlerCtx.funcName, 197 "interval", handlerCtx.interval) 198 continue 199 } 200 201 general.InfoS("handler is ready", 202 "groupName", groupName, 203 "handlerName", handlerName, 204 "funcName", handlerCtx.funcName, 205 "interval", handlerCtx.interval) 206 207 handlerCtx.ready = true 208 } 209 } 210 211 func StopHandlersByGroup(groupName string) { 212 handlerMtx.Lock() 213 defer handlerMtx.Unlock() 214 215 general.InfoS("called", "groupName", groupName) 216 217 for handlerName, handlerCtx := range handlerCtxs[groupName] { 218 if handlerCtx == nil { 219 general.Warningf("nil handlerCtx") 220 continue 221 } else if handlerCtx.ctx == nil { 222 general.InfoS("handler already stopped", 223 "groupName", groupName, 224 "handlerName", handlerName, 225 "funcName", handlerCtx.funcName, 226 "interval", handlerCtx.interval) 227 continue 228 } 229 230 general.InfoS("stop handler", 231 "groupName", groupName, 232 "handlerName", handlerName, 233 "funcName", handlerCtx.funcName, 234 "interval", handlerCtx.interval) 235 236 handlerCtx.cancel() 237 handlerCtx.cancel = nil 238 handlerCtx.ctx = nil 239 handlerCtx.ready = false 240 } 241 }