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{}