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 }