go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/mutexpool/pool.go (about) 1 // Copyright 2017 The LUCI Authors. 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 mutexpool implements P, a pool of keyed mutexes. These mutexes are 16 // created on-demand and deleted when no longer referenced, so the pool's 17 // maximum size is a function of the maximum number of concurrent mutexes 18 // held at any given time. 19 // 20 // Package mutexpool is useful when coordinating access to resources that are 21 // not managed by the accessor such as remote resource accesses. 22 package mutexpool 23 24 import ( 25 "fmt" 26 "sync" 27 ) 28 29 // P is a pool of keyed mutexes. The zero value is a valid empty pool. 30 // 31 // A user can grab an arbitrary Mutex's lock by calling WithMutex with a key. 32 // If something else currently holds that Mutex's lock, WithMutex will block 33 // until it can claim the lock. When a key is no longer in use, it will be 34 // removed from P. 35 type P struct { 36 mutexesLock sync.Mutex 37 mutexes map[any]*mutexEntry 38 } 39 40 func (pc *P) getConfigLock(key any) *mutexEntry { 41 // Does the lock already exist? 42 pc.mutexesLock.Lock() 43 defer pc.mutexesLock.Unlock() 44 45 if me := pc.mutexes[key]; me != nil { 46 me.count++ 47 if me.count == 0 { 48 panic(fmt.Errorf("mutex reference counter overflow")) 49 } 50 return me 51 } 52 53 if pc.mutexes == nil { 54 pc.mutexes = make(map[any]*mutexEntry) 55 } 56 me := &mutexEntry{ 57 count: 1, // Start with one ref. 58 } 59 pc.mutexes[key] = me 60 return me 61 } 62 63 func (pc *P) decRef(me *mutexEntry, key any) { 64 pc.mutexesLock.Lock() 65 defer pc.mutexesLock.Unlock() 66 67 me.count-- 68 if me.count == 0 { 69 delete(pc.mutexes, key) 70 } 71 } 72 73 // WithMutex locks the Mutex matching the specified key and executes fn while 74 // holding its lock. 75 // 76 // If a mutex for key doesn't exist, one will be created, and will be 77 // automatically cleaned up when no longer referenced. 78 func (pc *P) WithMutex(key any, fn func()) { 79 // Get a lock for this config key, and increment its reference. 80 me := pc.getConfigLock(key) 81 defer pc.decRef(me, key) 82 83 // Hold this lock's mutex and call "fn". 84 me.Lock() 85 defer me.Unlock() 86 87 fn() 88 } 89 90 type mutexEntry struct { 91 sync.Mutex 92 93 // count is the number of references to this mutexEntry. It is protected 94 // by P's lock. 95 count uint64 96 }