* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* Licensed 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 instancepool
import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"go.uber.org/zap"
"yuanrong.org/kernel/runtime/libruntime/api"
"yuanrong.org/kernel/pkg/common/faas_common/constant"
"yuanrong.org/kernel/pkg/common/faas_common/instanceconfig"
"yuanrong.org/kernel/pkg/common/faas_common/logger/log"
"yuanrong.org/kernel/pkg/common/faas_common/resspeckey"
"yuanrong.org/kernel/pkg/common/faas_common/snerror"
"yuanrong.org/kernel/pkg/common/faas_common/statuscode"
commonTypes "yuanrong.org/kernel/pkg/common/faas_common/types"
"yuanrong.org/kernel/pkg/common/faas_common/urnutils"
commonUtils "yuanrong.org/kernel/pkg/common/faas_common/utils"
"yuanrong.org/kernel/pkg/functionscaler/config"
"yuanrong.org/kernel/pkg/functionscaler/metrics"
"yuanrong.org/kernel/pkg/functionscaler/registry"
"yuanrong.org/kernel/pkg/functionscaler/state"
"yuanrong.org/kernel/pkg/functionscaler/stateinstance"
"yuanrong.org/kernel/pkg/functionscaler/types"
"yuanrong.org/kernel/pkg/functionscaler/utils"
)
const (
minSLATime = time.Duration(500) * time.Millisecond
)
var (
newOneShotInstancePoolFunc = NewOneShotInstancePool
newGenericInstancePoolFunc = NewGenericInstancePool
getAndDeleteStateFunc = func(pool InstancePool, stateID string) bool {
return pool.GetAndDeleteState(stateID)
}
)
type faasManagerInfo struct {
funcKey string
instanceID string
}
type PoolManager struct {
faasManagerInfo faasManagerInfo
instancePool map[string]InstancePool
instanceConfigRecord map[string]map[string]*instanceconfig.Configuration
stateLeaseManager map[string]*stateinstance.Leaser
abnormalInstanceMap *utils.TimeoutMap
leaseInterval time.Duration
stopCh <-chan struct{}
sync.RWMutex
}
func (pm *PoolManager) GetLeaseInterval() time.Duration {
return pm.leaseInterval
}
func getScaleDownWindow() time.Duration {
scaleUpWindow := time.Duration(config.GlobalConfig.AutoScaleConfig.SLAQuota) * time.Millisecond
if scaleUpWindow < minSLATime {
scaleUpWindow = minSLATime
}
scaleDownTime := time.Duration(config.GlobalConfig.AutoScaleConfig.ScaleDownTime) * time.Millisecond
if scaleDownTime < scaleUpWindow {
scaleDownTime = scaleUpWindow
}
return scaleDownTime
}
func NewPoolManager(stopCh <-chan struct{}) *PoolManager {
leaseInterval := time.Duration(config.GlobalConfig.LeaseSpan) * time.Millisecond
if leaseInterval < types.MinLeaseInterval {
leaseInterval = types.MinLeaseInterval
}
pm := &PoolManager{
faasManagerInfo: faasManagerInfo{},
instancePool: make(map[string]InstancePool, utils.DefaultMapSize),
instanceConfigRecord: make(map[string]map[string]*instanceconfig.Configuration, utils.DefaultMapSize),
stateLeaseManager: make(map[string]*stateinstance.Leaser),
abnormalInstanceMap: utils.NewTimeoutMap(time.Minute),
leaseInterval: leaseInterval,
stopCh: stopCh,
}
return pm
}
func (pm *PoolManager) RecoverInstancePool() {
s := state.GetState()
log.GetLogger().Infof("ready to recovery instance pool")
var wg sync.WaitGroup
registry.GlobalRegistry.InstanceRegistry.WaitForETCDList()
var deleteFunctions []*types.FunctionSpecification
for funcKey, val := range s.InstancePool {
var InstancePoolState *types.InstancePoolState
commonUtils.DeepCopyObj(val, &InstancePoolState)
funcSpec := registry.GlobalRegistry.GetFuncSpec(funcKey)
var deleteFuncFlag bool
if funcSpec == nil {
deleteFuncFlag = true
funcSpec = &types.FunctionSpecification{FuncKey: funcKey}
deleteFunctions = append(deleteFunctions, funcSpec)
}
pm.RLock()
_, exist := pm.instancePool[funcSpec.FuncKey]
pm.RUnlock()
if !exist {
if _, err := pm.processInstancePoolCreate(funcSpec); err != nil {
continue
}
}
log.GetLogger().Infof("now recover function :%s", funcSpec.FuncKey)
wg.Add(1)
go func() {
log.GetLogger().Infof("recover func: %s, isStateful: %v, deleteFuncFlag: %v",
funcSpec.FuncKey, funcSpec.FuncMetaData.IsStatefulFunction, deleteFuncFlag)
pm.instancePool[funcSpec.FuncKey].RecoverInstance(funcSpec, InstancePoolState, deleteFuncFlag, &wg)
pm.recoverStateLeaser(InstancePoolState.StateInstance, funcSpec)
}()
}
wg.Wait()
for _, function := range deleteFunctions {
pm.HandleFunctionEvent(registry.SubEventTypeDelete, function)
}
}
func (pm *PoolManager) recoverStateLeaser(stateInstance map[string]*types.Instance,
funcSpec *types.FunctionSpecification,
) {
for stateID, instance := range stateInstance {
pool := pm.instancePool[funcSpec.FuncKey]
if pool == nil {
log.GetLogger().Errorf("func %s, stateid %s: pool is nil! can't create leaser manager!",
funcSpec.FuncKey, stateID)
} else {
if instance.InstanceStatus.Code == int32(-1) {
log.GetLogger().Warnf("instance of stateID %s is existed!", stateID)
continue
}
log.GetLogger().Infof("func %s, stateId %s: create leaser manager for recovery, downwin %v",
funcSpec.FuncKey, stateID, getScaleDownWindow())
leaser := stateinstance.NewLeaser(funcSpec.InstanceMetaData.ConcurrentNum,
pool.DeleteStateInstance, stateID, instance.InstanceID, getScaleDownWindow())
pm.Lock()
leaser.Recover()
pm.stateLeaseManager[instance.InstanceID] = leaser
pm.Unlock()
}
}
}
func (pm *PoolManager) GetAndDeleteState(stateID string, funcKey string, funcSpec *types.FunctionSpecification,
logger api.FormatLogger,
) bool {
pm.Lock()
pool, exist := pm.instancePool[funcKey]
if !exist {
var err error
if funcSpec.InstanceMetaData.ScalePolicy == types.InstanceScalePolicyOneshot {
pool, err = newOneShotInstancePoolFunc(funcSpec, pm.faasManagerInfo)
} else {
pool, err = newGenericInstancePoolFunc(funcSpec, pm.faasManagerInfo)
}
if err != nil {
pm.Unlock()
return false
}
pm.instancePool[funcKey] = pool
}
pm.Unlock()
logger.Infof("getAndDeleteStateInstance, stateKey %s", stateID)
return getAndDeleteStateFunc(pool, stateID)
}
func (pm *PoolManager) CreateInstance(insCrtReq *types.InstanceCreateRequest) (*types.Instance, snerror.SNError) {
pm.RLock()
pool, exist := pm.instancePool[insCrtReq.FuncSpec.FuncKey]
pm.RUnlock()
if !exist {
return nil, snerror.New(statuscode.FuncMetaNotFoundErrCode, "pool not exist")
}
return pool.CreateInstance(insCrtReq)
}
func (pm *PoolManager) DeleteInstance(instance *types.Instance) snerror.SNError {
pm.RLock()
pool, exist := pm.instancePool[instance.FuncKey]
pm.RUnlock()
if !exist {
return snerror.New(statuscode.FuncMetaNotFoundErrCode, "pool not exist")
}
return pool.DeleteInstance(instance)
}
func (pm *PoolManager) AcquireInstanceThread(insAcqReq *types.InstanceAcquireRequest) (*types.InstanceAllocation,
snerror.SNError,
) {
pm.RLock()
pool, exist := pm.instancePool[insAcqReq.FuncSpec.FuncKey]
pm.RUnlock()
if !exist {
return nil, snerror.New(statuscode.FuncMetaNotFoundErrCode, "pool not exist")
}
insAlloc, acquireErr := pool.AcquireInstance(insAcqReq)
if acquireErr != nil {
return nil, acquireErr
}
return insAlloc, nil
}
func (pm *PoolManager) ReleaseStateThread(thread *types.InstanceAllocation) error {
leaser := pm.stateLeaseManager[thread.Instance.InstanceID]
if leaser == nil {
return errors.New("leaser is nil")
}
leaseID, err := getLeaseIdFromAllocationId(thread.AllocationID)
if err != nil {
return err
}
leaser.ReleaseLease(leaseID)
return nil
}
func (pm *PoolManager) RetainStateThread(thread *types.InstanceAllocation) error {
leaser := pm.stateLeaseManager[thread.Instance.InstanceID]
if leaser == nil {
return errors.New("leaser is nil")
}
leaseID, err := getLeaseIdFromAllocationId(thread.AllocationID)
if err != nil {
return err
}
return leaser.RetainLease(leaseID, pm.leaseInterval)
}
func getLeaseIdFromAllocationId(allocationId string) (int, error) {
parts := strings.Split(allocationId, "-")
if len(parts) < 1 {
return 0, fmt.Errorf("thread.AllocationID: %s invalid", allocationId)
}
numStr := strings.Replace(parts[len(parts)-1], "stateThread", "", 1)
leaseID, err := strconv.Atoi(numStr)
if err != nil {
return 0, fmt.Errorf("thread.AllocationID: %s invalid, err %v", allocationId, err)
}
return leaseID, nil
}
func (pm *PoolManager) ReleaseInstanceThread(insAlloc *types.InstanceAllocation) {
instance := insAlloc.Instance
pm.RLock()
pool, exist := pm.instancePool[instance.FuncKey]
pm.RUnlock()
if !exist {
log.GetLogger().Errorf("instance pool for function %s doesn't exist", instance.FuncKey)
return
}
pool.ReleaseInstance(insAlloc)
}
func (pm *PoolManager) QuerySession(funcKey string, sessionID string) (string, error) {
pm.RLock()
pool, exist := pm.instancePool[funcKey]
pm.RUnlock()
if !exist {
return "", fmt.Errorf("pool not exist for function %s", funcKey)
}
return pool.QuerySession(sessionID)
}
func (pm *PoolManager) ReleaseAbnormalInstance(instance *types.Instance, logger api.FormatLogger) {
pm.RLock()
pool, exist := pm.instancePool[instance.FuncKey]
pm.RUnlock()
if !exist {
logger.Warnf("instance pool for function %s doesn't exist", instance.FuncKey)
return
}
if _, ok := pm.abnormalInstanceMap.Get(instance.InstanceID); ok {
logger.Debugf("instance %s has stored into abnormal instance map, skip delete",
instance.InstanceID)
return
}
pm.abnormalInstanceMap.Set(instance.InstanceID, nil, time.Minute)
pool.HandleInstanceEvent(registry.SubEventTypeRemove, instance)
}
func (pm *PoolManager) HandleFunctionEvent(eventType registry.EventType, funcSpec *types.FunctionSpecification) {
log.GetLogger().Infof("handling function event type %s for function %s", eventType, funcSpec.FuncKey)
if eventType == registry.SubEventTypeUpdate {
pm.RLock()
pool, poolExist := pm.instancePool[funcSpec.FuncKey]
pm.RUnlock()
var err error
if !poolExist {
if pool, err = pm.processInstancePoolCreate(funcSpec); err != nil {
return
}
}
if poolExist {
pool.HandleFunctionEvent(eventType, funcSpec)
}
pm.RLock()
insConfigs, insConfigExist := pm.instanceConfigRecord[funcSpec.FuncKey]
if insConfigExist {
for _, insConfig := range insConfigs {
pool.HandleInstanceConfigEvent(eventType, insConfig)
}
}
pm.RUnlock()
}
if eventType == registry.SubEventTypeDelete {
pm.RLock()
pool, poolExist := pm.instancePool[funcSpec.FuncKey]
pm.RUnlock()
if poolExist {
pm.processInstancePoolDelete(funcSpec)
pool.HandleFunctionEvent(eventType, funcSpec)
}
metrics.ClearMetricsForFunction(funcSpec)
}
if eventType == registry.SubEventTypeSynced {
log.GetLogger().Infof("send function synced event")
registry.GlobalRegistry.FunctionRegistry.FinishEtcdList()
}
}
func (pm *PoolManager) processInstancePoolCreate(funcSpec *types.FunctionSpecification) (InstancePool, error) {
if funcSpec.FuncMetaData.VPCTriggerImage != "" &&
config.GlobalConfig.InstanceOperationBackend == constant.BackendTypeKernel {
pm.RLock()
faasMgrInfo := pm.faasManagerInfo
pm.RUnlock()
go handlePullTriggerCreate(faasMgrInfo, funcSpec)
}
var pool InstancePool
var err error
if funcSpec.InstanceMetaData.ScalePolicy == types.InstanceScalePolicyOneshot {
pool, err = newOneShotInstancePoolFunc(funcSpec, pm.faasManagerInfo)
} else {
pool, err = newGenericInstancePoolFunc(funcSpec, pm.faasManagerInfo)
}
if err != nil {
log.GetLogger().Errorf("failed to create instance pool of function %s error %s", funcSpec.FuncKey, err.Error())
return nil, err
}
pm.Lock()
pm.instancePool[funcSpec.FuncKey] = pool
pm.Unlock()
return pool, nil
}
func (pm *PoolManager) processInstancePoolDelete(funcSpec *types.FunctionSpecification) {
if funcSpec.FuncMetaData.VPCTriggerImage != "" &&
config.GlobalConfig.InstanceOperationBackend == constant.BackendTypeKernel {
pm.RLock()
faasMgrInfo := pm.faasManagerInfo
pm.RUnlock()
go handlePullTriggerDelete(faasMgrInfo, funcSpec)
}
pm.Lock()
delete(pm.instancePool, funcSpec.FuncKey)
delete(pm.instanceConfigRecord, funcSpec.FuncKey)
pm.Unlock()
state.Update(&types.InstancePoolStateInput{
FuncKey: funcSpec.FuncKey,
}, types.StateDelete)
}
func (pm *PoolManager) HandleInstanceEvent(eventType registry.EventType, insSpec *commonTypes.InstanceSpecification) {
if eventType == registry.SubEventTypeSynced {
for _, pool := range pm.instancePool {
pool.HandleInstanceEvent(eventType, nil)
}
return
}
items := strings.Split(insSpec.Function, utils.FuncKeyDelimiter)
if len(items) != utils.ValidFuncKeyLen {
return
}
if utils.IsFaaSManager(items[1]) {
if eventType != registry.SubEventTypeDelete {
pm.faasManagerInfo.funcKey = insSpec.Function
pm.faasManagerInfo.instanceID = insSpec.InstanceID
log.GetLogger().Infof("set faas manager info to %v", pm.faasManagerInfo)
for _, pool := range pm.instancePool {
pool.HandleFaaSManagerUpdate(pm.faasManagerInfo)
}
}
return
}
logger := log.GetLogger().With(zap.Any("InstanceID", insSpec.InstanceID))
logger.Infof("handling instance event, type %v, status %+v", eventType, insSpec.InstanceStatus)
pm.Lock()
pool, exist := pm.instancePool[insSpec.CreateOptions[types.FunctionKeyNote]]
if !exist {
logger.Warnf("instance pool for instance doesn't exist")
if eventType != registry.SubEventTypeDelete {
go DeleteUnexpectInstance(insSpec.ParentID, insSpec.InstanceID,
insSpec.CreateOptions[types.FunctionKeyNote], logger)
}
pm.Unlock()
return
}
pm.Unlock()
instance := utils.BuildInstanceFromInsSpec(insSpec, pool.GetFuncSpec())
switch eventType {
case registry.SubEventTypeUpdate:
metrics.EnsureLeaseRequestTotal(instance.MetricLabelValues)
metrics.EnsureConcurrencyGaugeWithLabel(pool.GetFuncSpec().FuncKey,
instance.ResKey.InvokeLabel, instance.MetricLabelValues)
case registry.SubEventTypeDelete:
metrics.ClearLeaseRequestTotal(instance.MetricLabelValues)
metrics.ClearConcurrencyGaugeWithLabel(instance.MetricLabelValues)
default:
logger.Debugf("no need update prometheus metric")
}
pm.updateStateLeaseMgrForHandleInstanceEvent(eventType, instance)
pool.HandleInstanceEvent(eventType, instance)
}
func (pm *PoolManager) updateStateLeaseMgrForHandleInstanceEvent(eventType registry.EventType,
instance *types.Instance,
) {
if eventType == registry.SubEventTypeDelete ||
(eventType == registry.SubEventTypeUpdate && instance.InstanceStatus.Code == 6) {
if stateLeaseManager, exist := pm.stateLeaseManager[instance.InstanceID]; exist {
log.GetLogger().Infof("terminate state lease manager for instance %s, funckey %s",
instance.InstanceID, instance.FuncKey)
stateLeaseManager.Terminate()
delete(pm.stateLeaseManager, instance.InstanceID)
}
}
}
func (pm *PoolManager) HandleSchedulerManaged(eventType registry.EventType,
insSpec *commonTypes.InstanceSpecification,
) {
log.GetLogger().Infof("handling scheduler managed event type %s, schedulerID:%s",
eventType, insSpec.InstanceID)
pm.Lock()
for _, p := range pm.instancePool {
p.handleManagedChange()
p.HandleFaaSSchedulerEvent()
}
pm.Unlock()
}
func (pm *PoolManager) HandleRolloutRatioChange(ratio int) {
log.GetLogger().Infof("handling scheduler ratio change %d", ratio)
pm.Lock()
defer pm.Unlock()
for _, p := range pm.instancePool {
p.handleRatioChange(ratio)
}
}
func (pm *PoolManager) HandleInstanceConfigEvent(eventType registry.EventType,
insConfig *instanceconfig.Configuration,
) {
logger := log.GetLogger().With(zap.Any("FuncKey", insConfig.FuncKey)).
With(zap.Any("eventType", eventType)).
With(zap.Any("InstanceLabel", insConfig.InstanceLabel))
logger.Infof("handling instance config event")
if eventType == registry.SubEventTypeSynced || eventType == registry.SubEventTypeCRSynced {
for _, pool := range pm.instancePool {
if eventType == registry.SubEventTypeSynced && !pool.GetFuncSpec().MetaFromCR ||
eventType == registry.SubEventTypeCRSynced && pool.GetFuncSpec().MetaFromCR {
pool.CleanOrphansInstanceQueue()
}
}
}
pm.RLock()
pool, exist := pm.instancePool[insConfig.FuncKey]
pm.RUnlock()
if eventType == registry.SubEventTypeUpdate {
pm.Lock()
if _, ok := pm.instanceConfigRecord[insConfig.FuncKey]; !ok {
pm.instanceConfigRecord[insConfig.FuncKey] = make(map[string]*instanceconfig.Configuration,
constant.DefaultMapSize)
}
pm.instanceConfigRecord[insConfig.FuncKey][insConfig.InstanceLabel] = insConfig
pm.Unlock()
if !exist {
logger.Warnf("instance pool for function doesn't exist")
return
}
pool.HandleInstanceConfigEvent(eventType, insConfig)
}
if eventType == registry.SubEventTypeDelete {
pm.Lock()
if _, ok := pm.instanceConfigRecord[insConfig.FuncKey]; !ok {
pm.Unlock()
return
}
delete(pm.instanceConfigRecord[insConfig.FuncKey], insConfig.InstanceLabel)
gi, ok := pm.instancePool[insConfig.FuncKey]
pm.Unlock()
if ok {
gi.HandleInstanceConfigEvent(eventType, insConfig)
}
}
}
func (pm *PoolManager) HandleAliasEvent(eventType registry.EventType, aliasUrn string) {
logger := log.GetLogger().With(zap.Any("", "HandleAliasEvent")).With(zap.Any("urn", aliasUrn))
logger.Infof("start")
var wg sync.WaitGroup
pm.RLock()
for funcKey, pool := range pm.instancePool {
tenantID := urnutils.GetTenantFromFuncKey(funcKey)
logger.Infof("handle alias event for funcKey %s, poolName %s, tenantID %s", funcKey,
pool.GetFuncSpec().FuncKey, tenantID)
if urnutils.CheckAliasUrnTenant(tenantID, aliasUrn) {
wg.Add(1)
go func(p InstancePool) {
defer wg.Done()
p.HandleAliasEvent(eventType, aliasUrn)
}(pool)
}
}
pm.RUnlock()
wg.Wait()
logger.Infof("finish")
}
func (pm *PoolManager) ReportMetrics(funcKey string, resKey resspeckey.ResSpecKey,
insMetrics *types.InstanceThreadMetrics,
) {
pm.RLock()
pool, exist := pm.instancePool[funcKey]
pm.RUnlock()
if !exist {
log.GetLogger().Errorf("failed to find pool for function %s", funcKey)
return
}
pool.UpdateInvokeMetrics(resKey, insMetrics)
}
func (pm *PoolManager) CheckMinInsAndReport(stopCh <-chan struct{}) {
go pm.checkMinInsRegularly(stopCh)
}
func getInstanceType(createOptions map[string]string) types.InstanceType {
instanceType, ok := createOptions[types.InstanceTypeNote]
if !ok {
return types.InstanceTypeUnknown
}
return types.InstanceType(instanceType)
}