github.com/livekit/protocol@v1.39.3/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(100 * 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(100 * 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  	{
   100  		m := &utils.Mutex{}
   101  		m.Lock()
   102  		go func() {
   103  			m.Unlock()
   104  		}()
   105  		require.Equal(t, 1, utils.NumMutexes())
   106  	}
   107  
   108  	for range 100 {
   109  		cleanupTest()
   110  		if utils.NumMutexes() == 0 {
   111  			break
   112  		}
   113  	}
   114  
   115  	require.Equal(t, 0, utils.NumMutexes())
   116  }
   117  
   118  func TestEmbeddedMutex(t *testing.T) {
   119  	t.Cleanup(cleanupTest)
   120  
   121  	foo := struct{ m utils.Mutex }{}
   122  	foo.m.Lock()
   123  	noop()
   124  	foo.m.Unlock()
   125  
   126  	bar := struct{ utils.Mutex }{}
   127  	bar.Lock()
   128  	noop()
   129  	bar.Unlock()
   130  }
   131  
   132  func TestContestedGlobalLock(t *testing.T) {
   133  	t.Cleanup(cleanupTest)
   134  
   135  	ms := make([]*utils.Mutex, 100)
   136  	for i := range ms {
   137  		m := &utils.Mutex{}
   138  		m.Lock()
   139  		noop()
   140  		m.Unlock()
   141  		ms[i] = m
   142  	}
   143  
   144  	var wg sync.WaitGroup
   145  	wg.Add(2)
   146  
   147  	go func() {
   148  		for i := 0; i < 100; i++ {
   149  			wg.Add(1)
   150  			go func() {
   151  				utils.ScanTrackedLocks(time.Minute)
   152  				wg.Done()
   153  			}()
   154  		}
   155  		wg.Done()
   156  	}()
   157  
   158  	go func() {
   159  		for i := 0; i < 100; i++ {
   160  			var m utils.Mutex
   161  			wg.Add(3)
   162  			for i := 0; i < 3; i++ {
   163  				go func() {
   164  					m.Lock()
   165  					noop()
   166  					m.Unlock()
   167  					wg.Done()
   168  				}()
   169  			}
   170  		}
   171  		wg.Done()
   172  	}()
   173  
   174  	wg.Wait()
   175  }
   176  
   177  func TestInitRace(t *testing.T) {
   178  	t.Cleanup(cleanupTest)
   179  
   180  	var wg sync.WaitGroup
   181  	for i := 0; i < 100; i++ {
   182  		var m utils.Mutex
   183  		wg.Add(3)
   184  		done := make(chan struct{})
   185  		for i := 0; i < 3; i++ {
   186  			go func() {
   187  				<-done
   188  				m.Lock()
   189  				noop()
   190  				m.Unlock()
   191  				wg.Done()
   192  			}()
   193  		}
   194  		close(done)
   195  		runtime.Gosched()
   196  	}
   197  
   198  	wg.Wait()
   199  }
   200  
   201  func BenchmarkLockTracker(b *testing.B) {
   202  	b.Run("wrapped mutex", func(b *testing.B) {
   203  		var m utils.Mutex
   204  		for i := 0; i < b.N; i++ {
   205  			m.Lock()
   206  			noop()
   207  			m.Unlock()
   208  		}
   209  	})
   210  	b.Run("wrapped rwmutex", func(b *testing.B) {
   211  		var m utils.RWMutex
   212  		for i := 0; i < b.N; i++ {
   213  			m.Lock()
   214  			noop()
   215  			m.Unlock()
   216  		}
   217  	})
   218  	b.Run("wrapped mutex init", func(b *testing.B) {
   219  		for i := 0; i < b.N; i++ {
   220  			var m utils.Mutex
   221  			m.Lock()
   222  			noop()
   223  			m.Unlock()
   224  		}
   225  	})
   226  	b.Run("wrapped rwmutex init", func(b *testing.B) {
   227  		for i := 0; i < b.N; i++ {
   228  			var m utils.RWMutex
   229  			m.Lock()
   230  			noop()
   231  			m.Unlock()
   232  		}
   233  	})
   234  
   235  	utils.ToggleLockTrackerStackTraces(true)
   236  	b.Run("wrapped mutex + stack trace", func(b *testing.B) {
   237  		var m utils.Mutex
   238  		for i := 0; i < b.N; i++ {
   239  			m.Lock()
   240  			noop()
   241  			m.Unlock()
   242  		}
   243  	})
   244  	b.Run("wrapped rwmutex + stack trace", func(b *testing.B) {
   245  		var m utils.RWMutex
   246  		for i := 0; i < b.N; i++ {
   247  			m.Lock()
   248  			noop()
   249  			m.Unlock()
   250  		}
   251  	})
   252  	b.Run("wrapped mutex init + stack trace", func(b *testing.B) {
   253  		for i := 0; i < b.N; i++ {
   254  			var m utils.Mutex
   255  			m.Lock()
   256  			noop()
   257  			m.Unlock()
   258  		}
   259  	})
   260  	b.Run("wrapped rwmutex init + stack trace", func(b *testing.B) {
   261  		for i := 0; i < b.N; i++ {
   262  			var m utils.RWMutex
   263  			m.Lock()
   264  			noop()
   265  			m.Unlock()
   266  		}
   267  	})
   268  	utils.ToggleLockTrackerStackTraces(false)
   269  
   270  	b.Run("native mutex", func(b *testing.B) {
   271  		var m sync.Mutex
   272  		for i := 0; i < b.N; i++ {
   273  			m.Lock()
   274  			noop()
   275  			m.Unlock()
   276  		}
   277  	})
   278  	b.Run("native rwmutex", func(b *testing.B) {
   279  		var m sync.RWMutex
   280  		for i := 0; i < b.N; i++ {
   281  			m.Lock()
   282  			noop()
   283  			m.Unlock()
   284  		}
   285  	})
   286  	b.Run("native mutex init", func(b *testing.B) {
   287  		for i := 0; i < b.N; i++ {
   288  			var m sync.Mutex
   289  			m.Lock()
   290  			noop()
   291  			m.Unlock()
   292  		}
   293  	})
   294  	b.Run("native rwmutex init", func(b *testing.B) {
   295  		for i := 0; i < b.N; i++ {
   296  			var m sync.RWMutex
   297  			m.Lock()
   298  			noop()
   299  			m.Unlock()
   300  		}
   301  	})
   302  }
   303  
   304  func BenchmarkGetBlocked(b *testing.B) {
   305  	for n := 100; n <= 1000000; n *= 100 {
   306  		n := n
   307  		b.Run(fmt.Sprintf("serial/%d", n), func(b *testing.B) {
   308  			cleanupTest()
   309  
   310  			ms := make([]*utils.Mutex, n)
   311  			for i := range ms {
   312  				m := &utils.Mutex{}
   313  				m.Lock()
   314  				noop()
   315  				m.Unlock()
   316  				ms[i] = m
   317  			}
   318  
   319  			b.ResetTimer()
   320  
   321  			for i := 0; i < b.N; i++ {
   322  				utils.ScanTrackedLocks(time.Minute)
   323  			}
   324  		})
   325  	}
   326  }