github.com/cilium/cilium@v1.16.2/pkg/ipam/service/allocator/bitmap.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 // Copyright The Kubernetes Authors. 4 5 package allocator 6 7 import ( 8 "errors" 9 "math/big" 10 "math/rand/v2" 11 12 "github.com/cilium/cilium/pkg/lock" 13 ) 14 15 // AllocationBitmap is a contiguous block of resources that can be allocated atomically. 16 // 17 // Each resource has an offset. The internal structure is a bitmap, with a bit for each offset. 18 // 19 // If a resource is taken, the bit at that offset is set to one. 20 // r.count is always equal to the number of set bits and can be recalculated at any time 21 // by counting the set bits in r.allocated. 22 // 23 // TODO: use RLE and compact the allocator to minimize space. 24 type AllocationBitmap struct { 25 // strategy carries the details of how to choose the next available item out of the range 26 strategy bitAllocator 27 // max is the maximum size of the usable items in the range 28 max int 29 // rangeSpec is the range specifier, matching RangeAllocation.Range 30 rangeSpec string 31 32 // lock guards the following members 33 lock lock.Mutex 34 // count is the number of currently allocated elements in the range 35 count int 36 // allocated is a bit array of the allocated items in the range 37 allocated *big.Int 38 } 39 40 // AllocationBitmap implements Interface and Snapshottable 41 var _ Interface = &AllocationBitmap{} 42 var _ Snapshottable = &AllocationBitmap{} 43 44 // bitAllocator represents a search strategy in the allocation map for a valid item. 45 type bitAllocator interface { 46 AllocateBit(allocated *big.Int, max, count int) (int, bool) 47 } 48 49 // NewAllocationMap creates an allocation bitmap using the random scan strategy. 50 func NewAllocationMap(max int, rangeSpec string) *AllocationBitmap { 51 a := AllocationBitmap{ 52 strategy: randomScanStrategy{}, 53 allocated: big.NewInt(0), 54 count: 0, 55 max: max, 56 rangeSpec: rangeSpec, 57 } 58 return &a 59 } 60 61 // NewContiguousAllocationMap creates an allocation bitmap using the contiguous scan strategy. 62 func NewContiguousAllocationMap(max int, rangeSpec string) *AllocationBitmap { 63 a := AllocationBitmap{ 64 strategy: contiguousScanStrategy{}, 65 allocated: big.NewInt(0), 66 count: 0, 67 max: max, 68 rangeSpec: rangeSpec, 69 } 70 return &a 71 } 72 73 // Allocate attempts to reserve the provided item. 74 // Returns true if it was allocated, false if it was already in use 75 func (r *AllocationBitmap) Allocate(offset int) (bool, error) { 76 r.lock.Lock() 77 defer r.lock.Unlock() 78 79 if r.allocated.Bit(offset) == 1 { 80 return false, nil 81 } 82 r.allocated = r.allocated.SetBit(r.allocated, offset, 1) 83 r.count++ 84 return true, nil 85 } 86 87 // AllocateNext reserves one of the items from the pool. 88 // (0, false, nil) may be returned if there are no items left. 89 func (r *AllocationBitmap) AllocateNext() (int, bool, error) { 90 r.lock.Lock() 91 defer r.lock.Unlock() 92 93 next, ok := r.strategy.AllocateBit(r.allocated, r.max, r.count) 94 if !ok { 95 return 0, false, nil 96 } 97 r.count++ 98 r.allocated = r.allocated.SetBit(r.allocated, next, 1) 99 return next, true, nil 100 } 101 102 // Release releases the item back to the pool. Releasing an 103 // unallocated item or an item out of the range is a no-op and 104 // returns no error. 105 func (r *AllocationBitmap) Release(offset int) { 106 r.lock.Lock() 107 defer r.lock.Unlock() 108 109 if r.allocated.Bit(offset) == 0 { 110 return 111 } 112 113 r.allocated = r.allocated.SetBit(r.allocated, offset, 0) 114 r.count-- 115 } 116 117 const ( 118 // Find the size of a big.Word in bytes. 119 notZero = uint64(^big.Word(0)) 120 wordPower = (notZero>>8)&1 + (notZero>>16)&1 + (notZero>>32)&1 121 wordSize = 1 << wordPower 122 ) 123 124 // ForEach calls the provided function for each allocated bit. The 125 // AllocationBitmap may not be modified while this loop is running. 126 func (r *AllocationBitmap) ForEach(fn func(int)) { 127 r.lock.Lock() 128 defer r.lock.Unlock() 129 130 words := r.allocated.Bits() 131 for wordIdx, word := range words { 132 bit := 0 133 for word > 0 { 134 if (word & 1) != 0 { 135 fn((wordIdx * wordSize * 8) + bit) 136 word = word &^ 1 137 } 138 bit++ 139 word = word >> 1 140 } 141 } 142 } 143 144 // Has returns true if the provided item is already allocated and a call 145 // to Allocate(offset) would fail. 146 func (r *AllocationBitmap) Has(offset int) bool { 147 r.lock.Lock() 148 defer r.lock.Unlock() 149 150 return r.allocated.Bit(offset) == 1 151 } 152 153 // Free returns the count of items left in the range. 154 func (r *AllocationBitmap) Free() int { 155 r.lock.Lock() 156 defer r.lock.Unlock() 157 return r.max - r.count 158 } 159 160 // Snapshot saves the current state of the pool. 161 func (r *AllocationBitmap) Snapshot() (string, []byte) { 162 r.lock.Lock() 163 defer r.lock.Unlock() 164 165 return r.rangeSpec, r.allocated.Bytes() 166 } 167 168 // Restore restores the pool to the previously captured state. 169 func (r *AllocationBitmap) Restore(rangeSpec string, data []byte) error { 170 r.lock.Lock() 171 defer r.lock.Unlock() 172 173 if r.rangeSpec != rangeSpec { 174 return errors.New("the provided range does not match the current range") 175 } 176 177 r.allocated = big.NewInt(0).SetBytes(data) 178 r.count = countBits(r.allocated) 179 180 return nil 181 } 182 183 // randomScanStrategy chooses a random address from the provided big.Int, and then 184 // scans forward looking for the next available address (it will wrap the range if 185 // necessary). 186 type randomScanStrategy struct{} 187 188 func (rss randomScanStrategy) AllocateBit(allocated *big.Int, max, count int) (int, bool) { 189 if count >= max { 190 return 0, false 191 } 192 offset := rand.IntN(max) 193 for i := 0; i < max; i++ { 194 at := (offset + i) % max 195 if allocated.Bit(at) == 0 { 196 return at, true 197 } 198 } 199 return 0, false 200 } 201 202 var _ bitAllocator = randomScanStrategy{} 203 204 // contiguousScanStrategy tries to allocate starting at 0 and filling in any gaps 205 type contiguousScanStrategy struct{} 206 207 func (contiguousScanStrategy) AllocateBit(allocated *big.Int, max, count int) (int, bool) { 208 if count >= max { 209 return 0, false 210 } 211 for i := 0; i < max; i++ { 212 if allocated.Bit(i) == 0 { 213 return i, true 214 } 215 } 216 return 0, false 217 } 218 219 var _ bitAllocator = contiguousScanStrategy{}