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 }