| // 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") | |
| } | |
| } | |
| } |