src/database/sql/closemu.go | 7 +++++-- src/database/sql/closemu_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/database/sql/closemu.go b/src/database/sql/closemu.go index 8590656dc48f83f341640e1863ecdaae94da7f4e..18a05575374259fa556890dade2a25d74fddc914 100644 --- a/src/database/sql/closemu.go +++ b/src/database/sql/closemu.go @@ -65,8 +65,11 @@ m.mu.Lock() defer m.mu.Unlock() for { x := m.state.Load() - if (x == 0 || x == 1) && m.state.CompareAndSwap(x, -1) { - return + if x == 0 || x == 1 { + if m.state.CompareAndSwap(x, -1) { + return + } + continue } // Set writer waiting bit and sleep. if x&1 == 0 && !m.state.CompareAndSwap(x, x|1) { diff --git a/src/database/sql/closemu_test.go b/src/database/sql/closemu_test.go index a53577754ccc4c96ae16115a16862953dee62cef..12a2860bffba32a4d55f6765e33124d5fa3540ad 100644 --- a/src/database/sql/closemu_test.go +++ b/src/database/sql/closemu_test.go @@ -5,8 +5,10 @@ package sql import ( + "runtime" "testing" "testing/synctest" + "time" ) func TestClosingMutex(t *testing.T) { @@ -127,6 +129,46 @@ } m.RUnlock() } }) +} + +func TestClosingMutexLockRLockRace(t *testing.T) { + oldProcs := runtime.GOMAXPROCS(2) + defer runtime.GOMAXPROCS(oldProcs) + + // Race Lock against readers entering and leaving. A stale state in Lock + // can leave the mutex in state 1 with no reader remaining to wake the + // writer, at which point the writer will never finish. + var m closingMutex + done := make(chan struct{}) + go func() { + defer close(done) + for range 10_000 { + m.Lock() + m.Unlock() + } + }() + + // Yield after each reader attempt so the writer can run on targets with + // one P and no goroutine preemption, such as wasm. + for range 100_000 { + select { + case <-done: + return + default: + } + if m.TryRLock() { + m.RUnlock() + } + runtime.Gosched() + } + select { + case <-done: + case <-time.After(time.Minute): + if state := m.state.Load(); state == 1 { + t.Fatalf("Lock left the mutex in state 1 with no reader to wake the writer") + } + t.Fatalf("Lock did not complete within one minute; mutex state is %v", m.state.Load()) + } } func TestClosingMutexPanics(t *testing.T) {