Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variable: fix sysvar datarace with deep copy #24732

Merged
merged 8 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions sessionctx/variable/sysvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/pingcap/tidb/util/collate"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/versioninfo"

morgo marked this conversation as resolved.
Show resolved Hide resolved
atomic2 "go.uber.org/atomic"
)

Expand Down Expand Up @@ -492,12 +493,40 @@ func UnregisterSysVar(name string) {
sysVarsLock.Unlock()
}

// copySv deep copies the sysvar struct to avoid a race
func copySv(src *SysVar) (dest *SysVar) {
return &SysVar{
morgo marked this conversation as resolved.
Show resolved Hide resolved
Scope: src.Scope,
Name: src.Name,
Value: src.Value,
Type: src.Type,
MinValue: src.MinValue,
MaxValue: src.MaxValue,
AutoConvertNegativeBool: src.AutoConvertNegativeBool,
AutoConvertOutOfRange: src.AutoConvertOutOfRange,
ReadOnly: src.ReadOnly,
PossibleValues: src.PossibleValues,
AllowEmpty: src.AllowEmpty,
AllowEmptyAll: src.AllowEmptyAll,
AllowAutoValue: src.AllowAutoValue,
Validation: src.Validation,
SetSession: src.SetSession,
SetGlobal: src.SetGlobal,
IsHintUpdatable: src.IsHintUpdatable,
Hidden: src.Hidden,
Aliases: src.Aliases,
}
}

// GetSysVar returns sys var info for name as key.
func GetSysVar(name string) *SysVar {
name = strings.ToLower(name)
sysVarsLock.RLock()
defer sysVarsLock.RUnlock()
return sysVars[name]
if sysVars[name] == nil {
return nil
}
return copySv(sysVars[name])
}

// SetSysVar sets a sysvar. This will not propagate to the cluster, so it should only be
Expand All @@ -515,7 +544,7 @@ func GetSysVars() map[string]*SysVar {
defer sysVarsLock.RUnlock()
copy := make(map[string]*SysVar, len(sysVars))
for name, sv := range sysVars {
copy[name] = sv
copy[name] = copySv(sv)
}
return copy
}
Expand Down
27 changes: 27 additions & 0 deletions sessionctx/variable/sysvar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,3 +555,30 @@ func (*testSysVarSuite) TestInstanceScopedVars(c *C) {
c.Assert(err, IsNil)
c.Assert(val, Equals, vars.TxnScope.GetVarValue())
}

// Calling GetSysVars/GetSysVar needs to return a deep copy, otherwise there will be data races.
// This is a bit unfortunate, since the only time the race occurs is in the testsuite (Enabling/Disabling SEM) and
// during startup (setting the .Value of ScopeNone variables). In future it might also be able
// to fix this by delaying the LoadSysVarCacheLoop start time until after the server is fully initialized.
func (*testSysVarSuite) TestDeepCopyGetSysVars(c *C) {
// Check GetSysVar
sv := SysVar{Scope: ScopeGlobal | ScopeSession, Name: "datarace", Value: On, Type: TypeBool}
RegisterSysVar(&sv)
svcopy := GetSysVar("datarace")
svcopy.Name = "datarace2"
c.Assert(sv.Name, Equals, "datarace")
c.Assert(GetSysVar("datarace").Name, Equals, "datarace")
UnregisterSysVar("datarace")

// Check GetSysVars
sv = SysVar{Scope: ScopeGlobal | ScopeSession, Name: "datarace", Value: On, Type: TypeBool}
RegisterSysVar(&sv)
for name, svcopy := range GetSysVars() {
if name == "datarace" {
svcopy.Name = "datarace2"
}
}
c.Assert(sv.Name, Equals, "datarace")
c.Assert(GetSysVar("datarace").Name, Equals, "datarace")
UnregisterSysVar("datarace")
}