// Copyright 2018 PingCAP, Inc. // // 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, // See the License for the specific language governing permissions and // limitations under the License. package domain import ( "fmt" "sync" "time" "github.com/pingcap/parser/ast" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/stmtsummary" "go.uber.org/zap" "golang.org/x/sync/singleflight" ) // GlobalVariableCache caches global variables. type GlobalVariableCache struct { sync.RWMutex lastModify time.Time rows []chunk.Row fields []*ast.ResultField // Unit test may like to disable it. disable bool SingleFight singleflight.Group } const globalVariableCacheExpiry time.Duration = 2 * time.Second // Update updates the global variable cache. func (gvc *GlobalVariableCache) Update(rows []chunk.Row, fields []*ast.ResultField) { gvc.Lock() gvc.lastModify = time.Now() gvc.rows = rows gvc.fields = fields gvc.Unlock() checkEnableServerGlobalVar(rows) } // Get gets the global variables from cache. func (gvc *GlobalVariableCache) Get() (succ bool, rows []chunk.Row, fields []*ast.ResultField) { gvc.RLock() defer gvc.RUnlock() if time.Since(gvc.lastModify) < globalVariableCacheExpiry { succ, rows, fields = !gvc.disable, gvc.rows, gvc.fields return } succ = false return } type loadResult struct { rows []chunk.Row fields []*ast.ResultField } // LoadGlobalVariables will load from global cache first, loadFn will be executed if cache is not valid func (gvc *GlobalVariableCache) LoadGlobalVariables(loadFn func() ([]chunk.Row, []*ast.ResultField, error)) ([]chunk.Row, []*ast.ResultField, error) { succ, rows, fields := gvc.Get() if succ { return rows, fields, nil } fn := func() (interface{}, error) { resRows, resFields, loadErr := loadFn() if loadErr != nil { return nil, loadErr } gvc.Update(resRows, resFields) return &loadResult{resRows, resFields}, nil } res, err, _ := gvc.SingleFight.Do("loadGlobalVariable", fn) if err != nil { return nil, nil, err } loadRes := res.(*loadResult) return loadRes.rows, loadRes.fields, nil } // Disable disables the global variable cache, used in test only. func (gvc *GlobalVariableCache) Disable() { gvc.Lock() defer gvc.Unlock() gvc.disable = true } // checkEnableServerGlobalVar processes variables that acts in server and global level. func checkEnableServerGlobalVar(rows []chunk.Row) { for _, row := range rows { sVal := "" if !row.IsNull(1) { sVal = row.GetString(1) } var err error switch row.GetString(0) { case variable.TiDBEnableStmtSummary: err = stmtsummary.StmtSummaryByDigestMap.SetEnabled(sVal, false) case variable.TiDBStmtSummaryInternalQuery: err = stmtsummary.StmtSummaryByDigestMap.SetEnabledInternalQuery(sVal, false) case variable.TiDBStmtSummaryRefreshInterval: err = stmtsummary.StmtSummaryByDigestMap.SetRefreshInterval(sVal, false) case variable.TiDBStmtSummaryHistorySize: err = stmtsummary.StmtSummaryByDigestMap.SetHistorySize(sVal, false) case variable.TiDBStmtSummaryMaxStmtCount: err = stmtsummary.StmtSummaryByDigestMap.SetMaxStmtCount(sVal, false) case variable.TiDBStmtSummaryMaxSQLLength: err = stmtsummary.StmtSummaryByDigestMap.SetMaxSQLLength(sVal, false) case variable.TiDBCapturePlanBaseline: variable.CapturePlanBaseline.Set(sVal, false) } if err != nil { logutil.BgLogger().Error(fmt.Sprintf("load global variable %s error", row.GetString(0)), zap.Error(err)) } } } // GetGlobalVarsCache gets the global variable cache. func (do *Domain) GetGlobalVarsCache() *GlobalVariableCache { return &do.gvc }