vitess.io/vitess@v0.16.2/go/pools/numbered.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package pools 18 19 import ( 20 "fmt" 21 "sync" 22 "time" 23 24 "vitess.io/vitess/go/cache" 25 ) 26 27 // Numbered allows you to manage resources by tracking them with numbers. 28 // There are no interface restrictions on what you can track. 29 type Numbered struct { 30 mu sync.Mutex 31 empty *sync.Cond // Broadcast when pool becomes empty 32 resources map[int64]*numberedWrapper 33 recentlyUnregistered *cache.LRUCache 34 } 35 36 type numberedWrapper struct { 37 val any 38 inUse bool 39 purpose string 40 } 41 42 type unregistered struct { 43 reason string 44 timeUnregistered time.Time 45 } 46 47 // NewNumbered creates a new numbered 48 func NewNumbered() *Numbered { 49 n := &Numbered{ 50 resources: make(map[int64]*numberedWrapper), 51 recentlyUnregistered: cache.NewLRUCache(1000, func(_ any) int64 { 52 return 1 53 }), 54 } 55 n.empty = sync.NewCond(&n.mu) 56 return n 57 } 58 59 // Register starts tracking a resource by the supplied id. 60 // It does not lock the object. 61 // It returns an error if the id already exists. 62 func (nu *Numbered) Register(id int64, val any) error { 63 // Optimistically assume we're not double registering. 64 resource := &numberedWrapper{ 65 val: val, 66 } 67 68 nu.mu.Lock() 69 defer nu.mu.Unlock() 70 71 _, ok := nu.resources[id] 72 if ok { 73 return fmt.Errorf("already present") 74 } 75 nu.resources[id] = resource 76 return nil 77 } 78 79 // Unregister forgets the specified resource. If the resource is not present, it's ignored. 80 func (nu *Numbered) Unregister(id int64, reason string) { 81 success := nu.unregister(id) 82 if success { 83 nu.recentlyUnregistered.Set( 84 fmt.Sprintf("%v", id), &unregistered{reason: reason, timeUnregistered: time.Now()}) 85 } 86 } 87 88 // unregister forgets the resource, if it exists. Returns whether or not the resource existed at 89 // time of Unregister. 90 func (nu *Numbered) unregister(id int64) bool { 91 nu.mu.Lock() 92 defer nu.mu.Unlock() 93 94 _, ok := nu.resources[id] 95 delete(nu.resources, id) 96 if len(nu.resources) == 0 { 97 nu.empty.Broadcast() 98 } 99 return ok 100 } 101 102 // Get locks the resource for use. It accepts a purpose as a string. 103 // If it cannot be found, it returns a "not found" error. If in use, 104 // it returns a "in use: purpose" error. 105 func (nu *Numbered) Get(id int64, purpose string) (val any, err error) { 106 nu.mu.Lock() 107 defer nu.mu.Unlock() 108 nw, ok := nu.resources[id] 109 if !ok { 110 if val, ok := nu.recentlyUnregistered.Get(fmt.Sprintf("%v", id)); ok { 111 unreg := val.(*unregistered) 112 return nil, fmt.Errorf("ended at %v (%v)", unreg.timeUnregistered.Format("2006-01-02 15:04:05.000 MST"), unreg.reason) 113 } 114 return nil, fmt.Errorf("not found") 115 } 116 if nw.inUse { 117 return nil, fmt.Errorf("in use: %s", nw.purpose) 118 } 119 nw.inUse = true 120 nw.purpose = purpose 121 return nw.val, nil 122 } 123 124 // Put unlocks a resource for someone else to use. 125 func (nu *Numbered) Put(id int64) bool { 126 nu.mu.Lock() 127 defer nu.mu.Unlock() 128 if nw, ok := nu.resources[id]; ok { 129 nw.inUse = false 130 nw.purpose = "" 131 return true 132 } 133 return false 134 } 135 136 // GetAll returns the list of all resources in the pool. 137 func (nu *Numbered) GetAll() (vals []any) { 138 nu.mu.Lock() 139 defer nu.mu.Unlock() 140 vals = make([]any, 0, len(nu.resources)) 141 for _, nw := range nu.resources { 142 vals = append(vals, nw.val) 143 } 144 return vals 145 } 146 147 // GetByFilter returns a list of resources that match the filter. 148 // It does not return any resources that are already locked. 149 func (nu *Numbered) GetByFilter(purpose string, match func(val any) bool) (vals []any) { 150 nu.mu.Lock() 151 defer nu.mu.Unlock() 152 for _, nw := range nu.resources { 153 if nw.inUse { 154 continue 155 } 156 if match(nw.val) { 157 nw.inUse = true 158 nw.purpose = purpose 159 vals = append(vals, nw.val) 160 } 161 } 162 return vals 163 } 164 165 // WaitForEmpty returns as soon as the pool becomes empty 166 func (nu *Numbered) WaitForEmpty() { 167 nu.mu.Lock() 168 defer nu.mu.Unlock() 169 for len(nu.resources) != 0 { 170 nu.empty.Wait() 171 } 172 } 173 174 // StatsJSON returns stats in JSON format 175 func (nu *Numbered) StatsJSON() string { 176 return fmt.Sprintf("{\"Size\": %v}", nu.Size()) 177 } 178 179 // Size returns the current size 180 func (nu *Numbered) Size() int64 { 181 nu.mu.Lock() 182 defer nu.mu.Unlock() 183 return int64(len(nu.resources)) 184 }