1// Copyright 2020 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//go:build goexperiment.staticlockranking 6 7package runtime 8 9import ( 10 "internal/runtime/atomic" 11 "unsafe" 12) 13 14const staticLockRanking = true 15 16// worldIsStopped is accessed atomically to track world-stops. 1 == world 17// stopped. 18var worldIsStopped atomic.Uint32 19 20// lockRankStruct is embedded in mutex 21type lockRankStruct struct { 22 // static lock ranking of the lock 23 rank lockRank 24 // pad field to make sure lockRankStruct is a multiple of 8 bytes, even on 25 // 32-bit systems. 26 pad int 27} 28 29// lockInit(l *mutex, rank int) sets the rank of lock before it is used. 30// If there is no clear place to initialize a lock, then the rank of a lock can be 31// specified during the lock call itself via lockWithRank(l *mutex, rank int). 32func lockInit(l *mutex, rank lockRank) { 33 l.rank = rank 34} 35 36func getLockRank(l *mutex) lockRank { 37 return l.rank 38} 39 40// lockWithRank is like lock(l), but allows the caller to specify a lock rank 41// when acquiring a non-static lock. 42// 43// Note that we need to be careful about stack splits: 44// 45// This function is not nosplit, thus it may split at function entry. This may 46// introduce a new edge in the lock order, but it is no different from any 47// other (nosplit) call before this call (including the call to lock() itself). 48// 49// However, we switch to the systemstack to record the lock held to ensure that 50// we record an accurate lock ordering. e.g., without systemstack, a stack 51// split on entry to lock2() would record stack split locks as taken after l, 52// even though l is not actually locked yet. 53func lockWithRank(l *mutex, rank lockRank) { 54 if l == &debuglock || l == &paniclk || l == &raceFiniLock { 55 // debuglock is only used for println/printlock(). Don't do lock 56 // rank recording for it, since print/println are used when 57 // printing out a lock ordering problem below. 58 // 59 // paniclk is only used for fatal throw/panic. Don't do lock 60 // ranking recording for it, since we throw after reporting a 61 // lock ordering problem. Additionally, paniclk may be taken 62 // after effectively any lock (anywhere we might panic), which 63 // the partial order doesn't cover. 64 // 65 // raceFiniLock is held while exiting when running 66 // the race detector. Don't do lock rank recording for it, 67 // since we are exiting. 68 lock2(l) 69 return 70 } 71 if rank == 0 { 72 rank = lockRankLeafRank 73 } 74 gp := getg() 75 // Log the new class. 76 systemstack(func() { 77 i := gp.m.locksHeldLen 78 if i >= len(gp.m.locksHeld) { 79 throw("too many locks held concurrently for rank checking") 80 } 81 gp.m.locksHeld[i].rank = rank 82 gp.m.locksHeld[i].lockAddr = uintptr(unsafe.Pointer(l)) 83 gp.m.locksHeldLen++ 84 85 // i is the index of the lock being acquired 86 if i > 0 { 87 checkRanks(gp, gp.m.locksHeld[i-1].rank, rank) 88 } 89 lock2(l) 90 }) 91} 92 93// nosplit to ensure it can be called in as many contexts as possible. 94// 95//go:nosplit 96func printHeldLocks(gp *g) { 97 if gp.m.locksHeldLen == 0 { 98 println("<none>") 99 return 100 } 101 102 for j, held := range gp.m.locksHeld[:gp.m.locksHeldLen] { 103 println(j, ":", held.rank.String(), held.rank, unsafe.Pointer(gp.m.locksHeld[j].lockAddr)) 104 } 105} 106 107// acquireLockRankAndM acquires a rank which is not associated with a mutex 108// lock. To maintain the invariant that an M with m.locks==0 does not hold any 109// lock-like resources, it also acquires the M. 110// 111// This function may be called in nosplit context and thus must be nosplit. 112// 113//go:nosplit 114func acquireLockRankAndM(rank lockRank) { 115 acquirem() 116 117 gp := getg() 118 // Log the new class. See comment on lockWithRank. 119 systemstack(func() { 120 i := gp.m.locksHeldLen 121 if i >= len(gp.m.locksHeld) { 122 throw("too many locks held concurrently for rank checking") 123 } 124 gp.m.locksHeld[i].rank = rank 125 gp.m.locksHeld[i].lockAddr = 0 126 gp.m.locksHeldLen++ 127 128 // i is the index of the lock being acquired 129 if i > 0 { 130 checkRanks(gp, gp.m.locksHeld[i-1].rank, rank) 131 } 132 }) 133} 134 135// checkRanks checks if goroutine g, which has mostly recently acquired a lock 136// with rank 'prevRank', can now acquire a lock with rank 'rank'. 137// 138//go:systemstack 139func checkRanks(gp *g, prevRank, rank lockRank) { 140 rankOK := false 141 if rank < prevRank { 142 // If rank < prevRank, then we definitely have a rank error 143 rankOK = false 144 } else if rank == lockRankLeafRank { 145 // If new lock is a leaf lock, then the preceding lock can 146 // be anything except another leaf lock. 147 rankOK = prevRank < lockRankLeafRank 148 } else { 149 // We've now verified the total lock ranking, but we 150 // also enforce the partial ordering specified by 151 // lockPartialOrder as well. Two locks with the same rank 152 // can only be acquired at the same time if explicitly 153 // listed in the lockPartialOrder table. 154 list := lockPartialOrder[rank] 155 for _, entry := range list { 156 if entry == prevRank { 157 rankOK = true 158 break 159 } 160 } 161 } 162 if !rankOK { 163 printlock() 164 println(gp.m.procid, " ======") 165 printHeldLocks(gp) 166 throw("lock ordering problem") 167 } 168} 169 170// See comment on lockWithRank regarding stack splitting. 171func unlockWithRank(l *mutex) { 172 if l == &debuglock || l == &paniclk || l == &raceFiniLock { 173 // See comment at beginning of lockWithRank. 174 unlock2(l) 175 return 176 } 177 gp := getg() 178 systemstack(func() { 179 found := false 180 for i := gp.m.locksHeldLen - 1; i >= 0; i-- { 181 if gp.m.locksHeld[i].lockAddr == uintptr(unsafe.Pointer(l)) { 182 found = true 183 copy(gp.m.locksHeld[i:gp.m.locksHeldLen-1], gp.m.locksHeld[i+1:gp.m.locksHeldLen]) 184 gp.m.locksHeldLen-- 185 break 186 } 187 } 188 if !found { 189 println(gp.m.procid, ":", l.rank.String(), l.rank, l) 190 throw("unlock without matching lock acquire") 191 } 192 unlock2(l) 193 }) 194} 195 196// releaseLockRankAndM releases a rank which is not associated with a mutex 197// lock. To maintain the invariant that an M with m.locks==0 does not hold any 198// lock-like resources, it also releases the M. 199// 200// This function may be called in nosplit context and thus must be nosplit. 201// 202//go:nosplit 203func releaseLockRankAndM(rank lockRank) { 204 gp := getg() 205 systemstack(func() { 206 found := false 207 for i := gp.m.locksHeldLen - 1; i >= 0; i-- { 208 if gp.m.locksHeld[i].rank == rank && gp.m.locksHeld[i].lockAddr == 0 { 209 found = true 210 copy(gp.m.locksHeld[i:gp.m.locksHeldLen-1], gp.m.locksHeld[i+1:gp.m.locksHeldLen]) 211 gp.m.locksHeldLen-- 212 break 213 } 214 } 215 if !found { 216 println(gp.m.procid, ":", rank.String(), rank) 217 throw("lockRank release without matching lockRank acquire") 218 } 219 }) 220 221 releasem(getg().m) 222} 223 224// nosplit because it may be called from nosplit contexts. 225// 226//go:nosplit 227func lockWithRankMayAcquire(l *mutex, rank lockRank) { 228 gp := getg() 229 if gp.m.locksHeldLen == 0 { 230 // No possibility of lock ordering problem if no other locks held 231 return 232 } 233 234 systemstack(func() { 235 i := gp.m.locksHeldLen 236 if i >= len(gp.m.locksHeld) { 237 throw("too many locks held concurrently for rank checking") 238 } 239 // Temporarily add this lock to the locksHeld list, so 240 // checkRanks() will print out list, including this lock, if there 241 // is a lock ordering problem. 242 gp.m.locksHeld[i].rank = rank 243 gp.m.locksHeld[i].lockAddr = uintptr(unsafe.Pointer(l)) 244 gp.m.locksHeldLen++ 245 checkRanks(gp, gp.m.locksHeld[i-1].rank, rank) 246 gp.m.locksHeldLen-- 247 }) 248} 249 250// nosplit to ensure it can be called in as many contexts as possible. 251// 252//go:nosplit 253func checkLockHeld(gp *g, l *mutex) bool { 254 for i := gp.m.locksHeldLen - 1; i >= 0; i-- { 255 if gp.m.locksHeld[i].lockAddr == uintptr(unsafe.Pointer(l)) { 256 return true 257 } 258 } 259 return false 260} 261 262// assertLockHeld throws if l is not held by the caller. 263// 264// nosplit to ensure it can be called in as many contexts as possible. 265// 266//go:nosplit 267func assertLockHeld(l *mutex) { 268 gp := getg() 269 270 held := checkLockHeld(gp, l) 271 if held { 272 return 273 } 274 275 // Crash from system stack to avoid splits that may cause 276 // additional issues. 277 systemstack(func() { 278 printlock() 279 print("caller requires lock ", l, " (rank ", l.rank.String(), "), holding:\n") 280 printHeldLocks(gp) 281 throw("not holding required lock!") 282 }) 283} 284 285// assertRankHeld throws if a mutex with rank r is not held by the caller. 286// 287// This is less precise than assertLockHeld, but can be used in places where a 288// pointer to the exact mutex is not available. 289// 290// nosplit to ensure it can be called in as many contexts as possible. 291// 292//go:nosplit 293func assertRankHeld(r lockRank) { 294 gp := getg() 295 296 for i := gp.m.locksHeldLen - 1; i >= 0; i-- { 297 if gp.m.locksHeld[i].rank == r { 298 return 299 } 300 } 301 302 // Crash from system stack to avoid splits that may cause 303 // additional issues. 304 systemstack(func() { 305 printlock() 306 print("caller requires lock with rank ", r.String(), "), holding:\n") 307 printHeldLocks(gp) 308 throw("not holding required lock!") 309 }) 310} 311 312// worldStopped notes that the world is stopped. 313// 314// Caller must hold worldsema. 315// 316// nosplit to ensure it can be called in as many contexts as possible. 317// 318//go:nosplit 319func worldStopped() { 320 if stopped := worldIsStopped.Add(1); stopped != 1 { 321 systemstack(func() { 322 print("world stop count=", stopped, "\n") 323 throw("recursive world stop") 324 }) 325 } 326} 327 328// worldStarted that the world is starting. 329// 330// Caller must hold worldsema. 331// 332// nosplit to ensure it can be called in as many contexts as possible. 333// 334//go:nosplit 335func worldStarted() { 336 if stopped := worldIsStopped.Add(-1); stopped != 0 { 337 systemstack(func() { 338 print("world stop count=", stopped, "\n") 339 throw("released non-stopped world stop") 340 }) 341 } 342} 343 344// nosplit to ensure it can be called in as many contexts as possible. 345// 346//go:nosplit 347func checkWorldStopped() bool { 348 stopped := worldIsStopped.Load() 349 if stopped > 1 { 350 systemstack(func() { 351 print("inconsistent world stop count=", stopped, "\n") 352 throw("inconsistent world stop count") 353 }) 354 } 355 356 return stopped == 1 357} 358 359// assertWorldStopped throws if the world is not stopped. It does not check 360// which M stopped the world. 361// 362// nosplit to ensure it can be called in as many contexts as possible. 363// 364//go:nosplit 365func assertWorldStopped() { 366 if checkWorldStopped() { 367 return 368 } 369 370 throw("world not stopped") 371} 372 373// assertWorldStoppedOrLockHeld throws if the world is not stopped and the 374// passed lock is not held. 375// 376// nosplit to ensure it can be called in as many contexts as possible. 377// 378//go:nosplit 379func assertWorldStoppedOrLockHeld(l *mutex) { 380 if checkWorldStopped() { 381 return 382 } 383 384 gp := getg() 385 held := checkLockHeld(gp, l) 386 if held { 387 return 388 } 389 390 // Crash from system stack to avoid splits that may cause 391 // additional issues. 392 systemstack(func() { 393 printlock() 394 print("caller requires world stop or lock ", l, " (rank ", l.rank.String(), "), holding:\n") 395 println("<no world stop>") 396 printHeldLocks(gp) 397 throw("no world stop or required lock!") 398 }) 399} 400