blob: a53577754ccc4c96ae16115a16862953dee62cef [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sql
import (
"testing"
"testing/synctest"
)
func TestClosingMutex(t *testing.T) {
start := func(t *testing.T, f func()) func() bool {
done := false
go func() {
f()
done = true
}()
return func() bool {
synctest.Wait()
return done
}
}
synctest.Test(t, func(t *testing.T) {
var m closingMutex
// RLock does not block RLock.
m.RLock()
m.RLock()
m.RUnlock()
m.RUnlock()
// RLock blocks Lock.
m.RLock()
lock1Done := start(t, m.Lock)
if lock1Done() {
t.Fatalf("m.Lock(): succeeded on RLocked mutex")
}
m.RLock()
m.RUnlock()
if lock1Done() {
t.Fatalf("m.Lock(): succeeded after one RUnlock, one RLock remains")
}
m.RUnlock()
if !lock1Done() {
t.Fatalf("m.Lock(): still blocking after all RUnlocks")
}
m.Unlock()
// Lock blocks RLock.
m.Lock()
rlock1Done := start(t, m.RLock)
rlock2Done := start(t, m.RLock)
if rlock1Done() || rlock2Done() {
t.Fatalf("m.RLock(): succeeded on Locked mutex")
}
m.Unlock()
if !rlock1Done() || !rlock2Done() {
t.Fatalf("m.RLock(): succeeded on Locked mutex")
}
m.RUnlock()
m.RUnlock()
// Lock blocks Lock.
m.Lock()
lock2Done := start(t, m.Lock)
if lock2Done() {
t.Fatalf("m.Lock(): succeeded on Locked mutex")
}
m.Unlock()
if !lock2Done() {
t.Fatalf("m.Lock(): still blocking after Unlock")
}
m.Unlock()
// Lock on RLocked mutex does not block RLock.
m.RLock()
lock3Done := start(t, m.Lock)
if lock3Done() {
t.Fatalf("m.Lock(): succeeded on RLocked mutex")
}
m.RLock()
m.RUnlock()
m.RUnlock()
if !lock3Done() {
t.Fatalf("m.Lock(): still blocking after RUnlock")
}
m.Unlock()
})
}
func TestClosingMutexLockStarvation(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
// Run this test for a few iterations, to avoid racy successes.
for range 100 {
var m closingMutex
// With the mutex RLocked, try to Lock it. Lock blocks.
m.RLock()
locked := false
go func() {
m.Lock()
locked = true
m.Unlock()
}()
synctest.Wait()
if locked {
t.Errorf("lock acquired while mutex is rlocked")
}
// Add and drop another RLock. Lock is still blocking.
m.RLock()
m.RUnlock()
if locked {
t.Errorf("lock acquired while mutex is double-rlocked")
}
// Drop and reacquire the RLock.
// The blocking Lock should always acquire the mutex
// before the RLock succeeds.
m.RUnlock()
m.RLock()
if !locked {
t.Errorf("lock not acquired when rlock dropped")
}
m.RUnlock()
}
})
}
func TestClosingMutexPanics(t *testing.T) {
for _, test := range []struct {
name string
f func()
}{{
name: "double RUnlock",
f: func() {
var m closingMutex
m.RLock()
m.RUnlock()
m.RUnlock()
},
}, {
name: "double Unlock",
f: func() {
var m closingMutex
m.Lock()
m.Unlock()
m.Unlock()
},
}} {
var got any
func() {
defer func() {
got = recover()
}()
test.f()
}()
if got == nil {
t.Errorf("no panic, want one")
}
}
}