github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/lock_tracker_test.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package utils_test
    16  
    17  import (
    18  	"fmt"
    19  	"runtime"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"github.com/livekit/protocol/utils"
    27  )
    28  
    29  func init() {
    30  	utils.EnableLockTracker()
    31  }
    32  
    33  func cleanupTest() {
    34  	runtime.GC()
    35  	time.Sleep(time.Millisecond)
    36  }
    37  
    38  func noop() {}
    39  
    40  func TestScanTrackedLocks(t *testing.T) {
    41  	t.Cleanup(cleanupTest)
    42  	require.Nil(t, utils.ScanTrackedLocks(time.Millisecond))
    43  
    44  	ms := make([]*utils.Mutex, 100)
    45  	for i := range ms {
    46  		m := &utils.Mutex{}
    47  		m.Lock()
    48  		noop()
    49  		m.Unlock()
    50  		ms[i] = m
    51  	}
    52  
    53  	go func() {
    54  		ms[50].Lock()
    55  		ms[50].Lock()
    56  	}()
    57  
    58  	time.Sleep(5 * time.Millisecond)
    59  	require.NotNil(t, utils.ScanTrackedLocks(time.Millisecond))
    60  
    61  	ms[50].Unlock()
    62  }
    63  
    64  func TestFirstLockStackTrace(t *testing.T) {
    65  	t.Cleanup(cleanupTest)
    66  	require.Nil(t, utils.ScanTrackedLocks(time.Millisecond))
    67  
    68  	utils.ToggleLockTrackerStackTraces(true)
    69  	defer utils.ToggleLockTrackerStackTraces(false)
    70  
    71  	m := &utils.Mutex{}
    72  
    73  	var deepLock func(n int)
    74  	deepLock = func(n int) {
    75  		if n > 0 {
    76  			deepLock(n - 1)
    77  		} else {
    78  			m.Lock()
    79  		}
    80  	}
    81  
    82  	go func() {
    83  		deepLock(5)
    84  		m.Lock()
    85  	}()
    86  
    87  	time.Sleep(5 * time.Millisecond)
    88  	locks := utils.ScanTrackedLocks(time.Millisecond)
    89  	require.NotNil(t, locks)
    90  	require.NotEqual(t, "", locks[0].FirstLockedAtStack())
    91  
    92  	m.Unlock()
    93  }
    94  
    95  func TestMutexFinalizer(t *testing.T) {
    96  	cleanupTest()
    97  	require.Equal(t, 0, utils.NumMutexes())
    98  
    99  	go func() {
   100  		m := &utils.Mutex{}
   101  		m.Lock()
   102  		go func() {
   103  			m.Unlock()
   104  		}()
   105  		require.Equal(t, 1, utils.NumMutexes())
   106  	}()
   107  
   108  	time.Sleep(time.Millisecond)
   109  	cleanupTest()
   110  
   111  	require.Equal(t, 0, utils.NumMutexes())
   112  }
   113  
   114  func TestEmbeddedMutex(t *testing.T) {
   115  	t.Cleanup(cleanupTest)
   116  
   117  	foo := struct{ m utils.Mutex }{}
   118  	foo.m.Lock()
   119  	noop()
   120  	foo.m.Unlock()
   121  
   122  	bar := struct{ utils.Mutex }{}
   123  	bar.Lock()
   124  	noop()
   125  	bar.Unlock()
   126  }
   127  
   128  func TestContestedGlobalLock(t *testing.T) {
   129  	t.Cleanup(cleanupTest)
   130  
   131  	ms := make([]*utils.Mutex, 100)
   132  	for i := range ms {
   133  		m := &utils.Mutex{}
   134  		m.Lock()
   135  		noop()
   136  		m.Unlock()
   137  		ms[i] = m
   138  	}
   139  
   140  	var wg sync.WaitGroup
   141  	wg.Add(2)
   142  
   143  	go func() {
   144  		for i := 0; i < 100; i++ {
   145  			wg.Add(1)
   146  			go func() {
   147  				utils.ScanTrackedLocks(time.Minute)
   148  				wg.Done()
   149  			}()
   150  		}
   151  		wg.Done()
   152  	}()
   153  
   154  	go func() {
   155  		for i := 0; i < 100; i++ {
   156  			var m utils.Mutex
   157  			wg.Add(3)
   158  			for i := 0; i < 3; i++ {
   159  				go func() {
   160  					m.Lock()
   161  					noop()
   162  					m.Unlock()
   163  					wg.Done()
   164  				}()
   165  			}
   166  		}
   167  		wg.Done()
   168  	}()
   169  
   170  	wg.Wait()
   171  }
   172  
   173  func TestInitRace(t *testing.T) {
   174  	t.Cleanup(cleanupTest)
   175  
   176  	var wg sync.WaitGroup
   177  	for i := 0; i < 100; i++ {
   178  		var m utils.Mutex
   179  		wg.Add(3)
   180  		done := make(chan struct{})
   181  		for i := 0; i < 3; i++ {
   182  			go func() {
   183  				<-done
   184  				m.Lock()
   185  				noop()
   186  				m.Unlock()
   187  				wg.Done()
   188  			}()
   189  		}
   190  		close(done)
   191  		runtime.Gosched()
   192  	}
   193  
   194  	wg.Wait()
   195  }
   196  
   197  func BenchmarkLockTracker(b *testing.B) {
   198  	b.Run("wrapped mutex", func(b *testing.B) {
   199  		var m utils.Mutex
   200  		for i := 0; i < b.N; i++ {
   201  			m.Lock()
   202  			noop()
   203  			m.Unlock()
   204  		}
   205  	})
   206  	b.Run("wrapped rwmutex", func(b *testing.B) {
   207  		var m utils.RWMutex
   208  		for i := 0; i < b.N; i++ {
   209  			m.Lock()
   210  			noop()
   211  			m.Unlock()
   212  		}
   213  	})
   214  	b.Run("wrapped mutex init", func(b *testing.B) {
   215  		for i := 0; i < b.N; i++ {
   216  			var m utils.Mutex
   217  			m.Lock()
   218  			noop()
   219  			m.Unlock()
   220  		}
   221  	})
   222  	b.Run("wrapped rwmutex init", func(b *testing.B) {
   223  		for i := 0; i < b.N; i++ {
   224  			var m utils.RWMutex
   225  			m.Lock()
   226  			noop()
   227  			m.Unlock()
   228  		}
   229  	})
   230  
   231  	utils.ToggleLockTrackerStackTraces(true)
   232  	b.Run("wrapped mutex + stack trace", func(b *testing.B) {
   233  		var m utils.Mutex
   234  		for i := 0; i < b.N; i++ {
   235  			m.Lock()
   236  			noop()
   237  			m.Unlock()
   238  		}
   239  	})
   240  	b.Run("wrapped rwmutex + stack trace", func(b *testing.B) {
   241  		var m utils.RWMutex
   242  		for i := 0; i < b.N; i++ {
   243  			m.Lock()
   244  			noop()
   245  			m.Unlock()
   246  		}
   247  	})
   248  	b.Run("wrapped mutex init + stack trace", func(b *testing.B) {
   249  		for i := 0; i < b.N; i++ {
   250  			var m utils.Mutex
   251  			m.Lock()
   252  			noop()
   253  			m.Unlock()
   254  		}
   255  	})
   256  	b.Run("wrapped rwmutex init + stack trace", func(b *testing.B) {
   257  		for i := 0; i < b.N; i++ {
   258  			var m utils.RWMutex
   259  			m.Lock()
   260  			noop()
   261  			m.Unlock()
   262  		}
   263  	})
   264  	utils.ToggleLockTrackerStackTraces(false)
   265  
   266  	b.Run("native mutex", func(b *testing.B) {
   267  		var m sync.Mutex
   268  		for i := 0; i < b.N; i++ {
   269  			m.Lock()
   270  			noop()
   271  			m.Unlock()
   272  		}
   273  	})
   274  	b.Run("native rwmutex", func(b *testing.B) {
   275  		var m sync.RWMutex
   276  		for i := 0; i < b.N; i++ {
   277  			m.Lock()
   278  			noop()
   279  			m.Unlock()
   280  		}
   281  	})
   282  	b.Run("native mutex init", func(b *testing.B) {
   283  		for i := 0; i < b.N; i++ {
   284  			var m sync.Mutex
   285  			m.Lock()
   286  			noop()
   287  			m.Unlock()
   288  		}
   289  	})
   290  	b.Run("native rwmutex init", func(b *testing.B) {
   291  		for i := 0; i < b.N; i++ {
   292  			var m sync.RWMutex
   293  			m.Lock()
   294  			noop()
   295  			m.Unlock()
   296  		}
   297  	})
   298  }
   299  
   300  func BenchmarkGetBlocked(b *testing.B) {
   301  	for n := 100; n <= 1000000; n *= 100 {
   302  		n := n
   303  		b.Run(fmt.Sprintf("serial/%d", n), func(b *testing.B) {
   304  			cleanupTest()
   305  
   306  			ms := make([]*utils.Mutex, n)
   307  			for i := range ms {
   308  				m := &utils.Mutex{}
   309  				m.Lock()
   310  				noop()
   311  				m.Unlock()
   312  				ms[i] = m
   313  			}
   314  
   315  			b.ResetTimer()
   316  
   317  			for i := 0; i < b.N; i++ {
   318  				utils.ScanTrackedLocks(time.Minute)
   319  			}
   320  		})
   321  	}
   322  }