Source file src/database/sql/closemu_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package sql
     6  
     7  import (
     8  	"runtime"
     9  	"testing"
    10  	"testing/synctest"
    11  	"time"
    12  )
    13  
    14  func TestClosingMutex(t *testing.T) {
    15  	start := func(t *testing.T, f func()) func() bool {
    16  		done := false
    17  		go func() {
    18  			f()
    19  			done = true
    20  		}()
    21  		return func() bool {
    22  			synctest.Wait()
    23  			return done
    24  		}
    25  	}
    26  
    27  	synctest.Test(t, func(t *testing.T) {
    28  		var m closingMutex
    29  
    30  		// RLock does not block RLock.
    31  		m.RLock()
    32  		m.RLock()
    33  		m.RUnlock()
    34  		m.RUnlock()
    35  
    36  		// RLock blocks Lock.
    37  		m.RLock()
    38  		lock1Done := start(t, m.Lock)
    39  		if lock1Done() {
    40  			t.Fatalf("m.Lock(): succeeded on RLocked mutex")
    41  		}
    42  		m.RLock()
    43  		m.RUnlock()
    44  		if lock1Done() {
    45  			t.Fatalf("m.Lock(): succeeded after one RUnlock, one RLock remains")
    46  		}
    47  		m.RUnlock()
    48  		if !lock1Done() {
    49  			t.Fatalf("m.Lock(): still blocking after all RUnlocks")
    50  		}
    51  		m.Unlock()
    52  
    53  		// Lock blocks RLock.
    54  		m.Lock()
    55  		rlock1Done := start(t, m.RLock)
    56  		rlock2Done := start(t, m.RLock)
    57  		if rlock1Done() || rlock2Done() {
    58  			t.Fatalf("m.RLock(): succeeded on Locked mutex")
    59  		}
    60  		m.Unlock()
    61  		if !rlock1Done() || !rlock2Done() {
    62  			t.Fatalf("m.RLock(): succeeded on Locked mutex")
    63  		}
    64  		m.RUnlock()
    65  		m.RUnlock()
    66  
    67  		// Lock blocks Lock.
    68  		m.Lock()
    69  		lock2Done := start(t, m.Lock)
    70  		if lock2Done() {
    71  			t.Fatalf("m.Lock(): succeeded on Locked mutex")
    72  		}
    73  		m.Unlock()
    74  		if !lock2Done() {
    75  			t.Fatalf("m.Lock(): still blocking after Unlock")
    76  		}
    77  		m.Unlock()
    78  
    79  		// Lock on RLocked mutex does not block RLock.
    80  		m.RLock()
    81  		lock3Done := start(t, m.Lock)
    82  		if lock3Done() {
    83  			t.Fatalf("m.Lock(): succeeded on RLocked mutex")
    84  		}
    85  		m.RLock()
    86  		m.RUnlock()
    87  		m.RUnlock()
    88  		if !lock3Done() {
    89  			t.Fatalf("m.Lock(): still blocking after RUnlock")
    90  		}
    91  		m.Unlock()
    92  	})
    93  }
    94  
    95  func TestClosingMutexLockStarvation(t *testing.T) {
    96  	synctest.Test(t, func(t *testing.T) {
    97  		// Run this test for a few iterations, to avoid racy successes.
    98  		for range 100 {
    99  			var m closingMutex
   100  
   101  			// With the mutex RLocked, try to Lock it. Lock blocks.
   102  			m.RLock()
   103  			locked := false
   104  			go func() {
   105  				m.Lock()
   106  				locked = true
   107  				m.Unlock()
   108  			}()
   109  			synctest.Wait()
   110  			if locked {
   111  				t.Errorf("lock acquired while mutex is rlocked")
   112  			}
   113  
   114  			// Add and drop another RLock. Lock is still blocking.
   115  			m.RLock()
   116  			m.RUnlock()
   117  			if locked {
   118  				t.Errorf("lock acquired while mutex is double-rlocked")
   119  			}
   120  
   121  			// Drop and reacquire the RLock.
   122  			// The blocking Lock should always acquire the mutex
   123  			// before the RLock succeeds.
   124  			m.RUnlock()
   125  			m.RLock()
   126  			if !locked {
   127  				t.Errorf("lock not acquired when rlock dropped")
   128  			}
   129  			m.RUnlock()
   130  		}
   131  	})
   132  }
   133  
   134  func TestClosingMutexLockRLockRace(t *testing.T) {
   135  	oldProcs := runtime.GOMAXPROCS(2)
   136  	defer runtime.GOMAXPROCS(oldProcs)
   137  
   138  	// Race Lock against readers entering and leaving. A stale state in Lock
   139  	// can leave the mutex in state 1 with no reader remaining to wake the
   140  	// writer, at which point the writer will never finish.
   141  	var m closingMutex
   142  	done := make(chan struct{})
   143  	go func() {
   144  		defer close(done)
   145  		for range 10_000 {
   146  			m.Lock()
   147  			m.Unlock()
   148  		}
   149  	}()
   150  
   151  	// Yield after each reader attempt so the writer can run on targets with
   152  	// one P and no goroutine preemption, such as wasm.
   153  	for range 100_000 {
   154  		select {
   155  		case <-done:
   156  			return
   157  		default:
   158  		}
   159  		if m.TryRLock() {
   160  			m.RUnlock()
   161  		}
   162  		runtime.Gosched()
   163  	}
   164  	select {
   165  	case <-done:
   166  	case <-time.After(time.Minute):
   167  		if state := m.state.Load(); state == 1 {
   168  			t.Fatalf("Lock left the mutex in state 1 with no reader to wake the writer")
   169  		}
   170  		t.Fatalf("Lock did not complete within one minute; mutex state is %v", m.state.Load())
   171  	}
   172  }
   173  
   174  func TestClosingMutexPanics(t *testing.T) {
   175  	for _, test := range []struct {
   176  		name string
   177  		f    func()
   178  	}{{
   179  		name: "double RUnlock",
   180  		f: func() {
   181  			var m closingMutex
   182  			m.RLock()
   183  			m.RUnlock()
   184  			m.RUnlock()
   185  		},
   186  	}, {
   187  		name: "double Unlock",
   188  		f: func() {
   189  			var m closingMutex
   190  			m.Lock()
   191  			m.Unlock()
   192  			m.Unlock()
   193  		},
   194  	}} {
   195  		var got any
   196  		func() {
   197  			defer func() {
   198  				got = recover()
   199  			}()
   200  			test.f()
   201  		}()
   202  		if got == nil {
   203  			t.Errorf("no panic, want one")
   204  		}
   205  	}
   206  }
   207  

View as plain text