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  }