github.com/amazechain/amc@v0.1.3/internal/p2p/leaky-bucket/collector.go (about) 1 package leakybucket 2 3 import ( 4 "container/heap" 5 "sync" 6 "time" 7 ) 8 9 //TODO: Finer grained locking. 10 11 type bucketMap map[string]*LeakyBucket 12 13 // A Collector can keep track of multiple LeakyBucket's. The caller does not 14 // directly interact with the buckets, but instead addresses them by a string 15 // key (e.g. IP address, hostname, hash, etc.) that is passed to most Collector 16 // methods. 17 // 18 // All Collector methods are goroutine safe. 19 type Collector struct { 20 buckets bucketMap 21 heap priorityQueue 22 rate float64 23 capacity int64 24 period time.Duration 25 lock sync.Mutex 26 quit chan bool 27 } 28 29 // NewCollector creates a new Collector. When new buckets are created within 30 // the Collector, they will be assigned the capacity and rate of the Collector. 31 // A Collector does not provide a way to change the rate or capacity of 32 // bucket's within it. If different rates or capacities are required, either 33 // use multiple Collector's or manage your own LeakyBucket's. 34 // 35 // If deleteEmptyBuckets is true, a concurrent goroutine will be run that 36 // watches for bucket's that become empty and automatically removes them, 37 // freeing up memory resources. 38 func NewCollector(rate float64, capacity int64, period time.Duration, deleteEmptyBuckets bool) *Collector { 39 c := &Collector{ 40 buckets: make(bucketMap), 41 heap: make(priorityQueue, 0, 4096), 42 rate: rate, 43 capacity: capacity, 44 period: period, 45 quit: make(chan bool), 46 } 47 48 if deleteEmptyBuckets { 49 c.PeriodicPrune() 50 } 51 52 return c 53 } 54 55 // Free releases the collector's resources. If the collector was created with 56 // deleteEmptyBuckets = true, then the goroutine looking for empty buckets, 57 // will be stopped. 58 func (c *Collector) Free() { 59 c.Reset() 60 close(c.quit) 61 } 62 63 // Reset removes all internal buckets and resets the collector back to as if it 64 // was just created. 65 func (c *Collector) Reset() { 66 c.lock.Lock() 67 defer c.lock.Unlock() 68 69 // Let the garbage collector do all the work. 70 c.buckets = make(bucketMap) 71 c.heap = make(priorityQueue, 0, 4096) 72 } 73 74 // Capacity returns the collector's capacity. 75 func (c *Collector) Capacity() int64 { 76 return c.capacity 77 } 78 79 // Rate returns the collector's rate. 80 func (c *Collector) Rate() float64 { 81 return c.rate 82 } 83 84 // Remaining returns the remaining capacity of the internal bucket associated 85 // with key. If key is not associated with a bucket internally, it is treated 86 // as being empty. 87 func (c *Collector) Remaining(key string) int64 { 88 return c.capacity - c.Count(key) 89 } 90 91 // Count returns the count of the internal bucket associated with key. If key 92 // is not associated with a bucket internally, it is treated as being empty. 93 func (c *Collector) Count(key string) int64 { 94 c.lock.Lock() 95 defer c.lock.Unlock() 96 97 b, ok := c.buckets[key] 98 if !ok || b == nil { 99 return 0 100 } 101 102 return b.Count() 103 } 104 105 // TillEmpty returns how much time must pass until the internal bucket 106 // associated with key is empty. If key is not associated with a bucket 107 // internally, it is treated as being empty. 108 func (c *Collector) TillEmpty(key string) time.Duration { 109 c.lock.Lock() 110 defer c.lock.Unlock() 111 112 b, ok := c.buckets[key] 113 if !ok || b == nil { 114 return 0 115 } 116 117 return b.TillEmpty() 118 } 119 120 // Remove deletes the internal bucket associated with key. If key is not 121 // associated with a bucket internally, nothing is done. 122 func (c *Collector) Remove(key string) { 123 c.lock.Lock() 124 defer c.lock.Unlock() 125 126 b, ok := c.buckets[key] 127 if !ok || b == nil { 128 return 129 } 130 131 delete(c.buckets, b.key) 132 heap.Remove(&c.heap, b.index) 133 } 134 135 // Add 'amount' to the internal bucket associated with key, up to it's 136 // capacity. Returns how much was added to the bucket. If the return is less 137 // than 'amount', then the bucket's capacity was reached. 138 // 139 // If key is not associated with a bucket internally, a new bucket is created 140 // and amount is added to it. 141 func (c *Collector) Add(key string, amount int64) int64 { 142 c.lock.Lock() 143 defer c.lock.Unlock() 144 145 b, ok := c.buckets[key] 146 147 if !ok || b == nil { 148 // Create a new bucket. 149 b = &LeakyBucket{ 150 key: key, 151 capacity: c.capacity, 152 rate: c.rate, 153 period: c.period, 154 p: now(), 155 } 156 c.heap.Push(b) 157 c.buckets[key] = b 158 } 159 160 n := b.Add(amount) 161 162 if n > 0 { 163 heap.Fix(&c.heap, b.index) 164 } 165 166 return n 167 } 168 169 // Prune removes all empty buckets in the collector. 170 func (c *Collector) Prune() { 171 c.lock.Lock() 172 for c.heap.Peak() != nil { 173 b := c.heap.Peak() 174 175 if now().Before(b.p) { 176 // The bucket isn't empty. 177 break 178 } 179 180 // The bucket should be empty. 181 delete(c.buckets, b.key) 182 heap.Remove(&c.heap, b.index) 183 } 184 c.lock.Unlock() 185 } 186 187 // PeriodicPrune runs a concurrent goroutine that calls Prune() at the given 188 // time interval. 189 func (c *Collector) PeriodicPrune() { 190 go func() { 191 ticker := time.NewTicker(c.period) 192 for { 193 select { 194 case <-ticker.C: 195 c.Prune() 196 case <-c.quit: 197 ticker.Stop() 198 return 199 } 200 } 201 }() 202 }