Skip to content

Commit 482192e

Browse files
craig[bot]ecwall
andcommitted
108032: sql: Make tochar.FormatCache thread safe r=otan,rafiss a=ecwall Fixes cockroachdb#95424 `FormatCache.lookup()` previously wrapped calls to `cache.UnorderedCache.Get()` in `RWMutex.RLock()`/`RWMutex.RUnlock()` which allowed for data races because `UnorderedCache.Get()` can modify the state of its LRU cache. This PR fixes the race condition by changing the `RWMutex` to a `Mutex` to handle cases where the LRU cache is modified. Release note (bug fix): Fix nil pointer dereference caused by race condition when using to_char builtin. Co-authored-by: Evan Wall <[email protected]>
2 parents 729212e + 3200fa7 commit 482192e

File tree

2 files changed

+18
-3
lines changed

2 files changed

+18
-3
lines changed

pkg/util/tochar/cache.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ const maxCacheKeySize = 100
2020
// FormatCache is a cache used to store parsing info used for to_char.
2121
// It is thread safe, and is safe to use by `nil` caches.
2222
type FormatCache struct {
23+
// mu must be a Mutex, not a RWMutex because Get can modify the LRU cache.
2324
mu struct {
24-
syncutil.RWMutex
25+
syncutil.Mutex
2526
cache *cache.UnorderedCache
2627
}
2728
}
@@ -41,8 +42,8 @@ func NewFormatCache(size int) *FormatCache {
4142
func (pc *FormatCache) lookup(fmtString string) []formatNode {
4243
if pc != nil && len(fmtString) <= maxCacheKeySize {
4344
if ret, ok := func() ([]formatNode, bool) {
44-
pc.mu.RLock()
45-
defer pc.mu.RUnlock()
45+
pc.mu.Lock()
46+
defer pc.mu.Unlock()
4647
ret, ok := pc.mu.cache.Get(fmtString)
4748
if ok {
4849
return ret.([]formatNode), true

pkg/util/tochar/cache_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,17 @@ func TestFormatCache(t *testing.T) {
4040
_, ok = c.mu.cache.Get("HH24")
4141
require.True(t, ok)
4242
}
43+
44+
// TestFormatCacheLookupThreadSafe is non-deterministic. Flakes indicate that
45+
// FormatCache.lookup is not thread safe.
46+
// See https://github.com/cockroachdb/cockroach/issues/95424
47+
func TestFormatCacheLookupThreadSafe(t *testing.T) {
48+
formats := []string{"HH12", "HH24", "MI"}
49+
c := NewFormatCache(len(formats) - 1)
50+
for i := 0; i < 100_000; i++ {
51+
go func(i int) {
52+
format := formats[i%len(formats)]
53+
c.lookup(format)
54+
}(i)
55+
}
56+
}

0 commit comments

Comments
 (0)