diff --git a/projects/RabbitMQ.Client/Util/IntAllocator.cs b/projects/RabbitMQ.Client/Util/IntAllocator.cs index 3bbcc53e2..eafba7753 100644 --- a/projects/RabbitMQ.Client/Util/IntAllocator.cs +++ b/projects/RabbitMQ.Client/Util/IntAllocator.cs @@ -30,183 +30,83 @@ //--------------------------------------------------------------------------- using System; -using System.Diagnostics; +using System.Collections; namespace RabbitMQ.Client.Util { - /** - * A class for allocating integer IDs in a given range. - */ + /// + /// + /// internal class IntAllocator { - private readonly int[] _unsorted; - private IntervalList? _base; - private int _unsortedCount = 0; - - /** - * A class representing a list of inclusive intervals - */ - - /** - * Creates an IntAllocator allocating integer IDs within the inclusive range [start, end] - */ - - public IntAllocator(int start, int end) + private readonly int _loRange; // the integer that bit 0 represents + private readonly int _hiRange; // one more than the integer the highest bit represents + private readonly int _numberOfBits; // + + /// + /// A bit is SET/true in _freeSet if the corresponding integer is FREE + /// A bit is UNSET/false in freeSet if the corresponding integer is ALLOCATED + /// + private readonly BitArray _freeSet; + + /// + /// Creates an IntAllocator allocating integer IDs within the + /// inclusive range [bottom, top]. + /// + /// lower end of range + /// upper end of range (incusive) + /// + public IntAllocator(int bottom, int top) { - if (start > end) + if (bottom > top) { - throw new ArgumentException($"illegal range [{start}, {end}]"); + throw new ArgumentException($"illegal range [{bottom}, {top}]"); } - // Fairly arbitrary heuristic for a good size for the unsorted set. - _unsorted = new int[Math.Max(32, (int)Math.Sqrt(end - start))]; - _base = new IntervalList(start, end); + _loRange = bottom; + _hiRange = top + 1; + _numberOfBits = _hiRange - _loRange; + _freeSet = new BitArray(_numberOfBits, true); // All integers are FREE initially } - /** - * Allocate a fresh integer from the range, or return -1 if no more integers - * are available. This operation is guaranteed to run in O(1) - */ - public int Allocate() { - if (_unsortedCount > 0) - { - return _unsorted[--_unsortedCount]; - } - else if (_base != null) - { - int result = _base.Start; - if (_base.Start == _base.End) - { - _base = _base.Next; - } - else - { - _base.Start++; - } - return result; - } - else + int setIndex = nextSetBit(); + if (setIndex < 0) // no free integers are available { return -1; } + _freeSet.Set(setIndex, false); + return setIndex + _loRange; } - /** - * Make the provided integer available for allocation again. This operation - * runs in amortized O(sqrt(range size)) time: About every sqrt(range size) - * operations will take O(range_size + number of intervals) to complete and - * the rest run in constant time. - * - * No error checking is performed, so if you double Free or Free an integer - * that was not originally Allocated the results are undefined. Sorry. - */ - - public void Free(int id) - { - if (_unsortedCount >= _unsorted.Length) - { - Flush(); - } - _unsorted[_unsortedCount++] = id; - } - - private void Flush() + /// + /// Makes the provided integer available for allocation again. + /// + /// the previously allocated integer to free + public void Free(int reservation) { - if (_unsortedCount > 0) - { - _base = IntervalList.Merge(_base, IntervalList.FromArray(_unsorted, _unsortedCount)); - _unsortedCount = 0; - } + int setIndex = reservation - _loRange; + _freeSet.Set(setIndex, true); // true means "unallocated" } - - public class IntervalList + /// + /// Note: this is different than the Java implementation, because we need to + /// preserve the prior behavior of always reserving low integers, if available. + /// See Test.Integration.AllocateAfterFreeingMany + /// + /// index of the next unallocated bit + private int nextSetBit() { - public int End; - - // Invariant: If Next != Null then Next.Start > this.End + 1 - public IntervalList? Next; - public int Start; - - public IntervalList(int start, int end) + for (int i = 0; i < _freeSet.Count; i++) { - Start = start; - End = end; - } - - // Destructively merge two IntervalLists. - // Invariant: None of the Intervals in the two lists may overlap - // intervals in this list. - - public static IntervalList? FromArray(int[] xs, int length) - { - Array.Sort(xs, 0, length); - - IntervalList? result = null; - IntervalList? current = null; - - int i = 0; - while (i < length) + if (_freeSet.Get(i)) // true means "unallocated" { - int start = i; - while ((i < length - 1) && (xs[i + 1] == xs[i] + 1)) - { - i++; - } - - var interval = new IntervalList(xs[start], xs[i]); - - if (result is null) - { - result = interval; - current = interval; - } - else - { - current!.Next = interval; - current = interval; - } - i++; + return i; } - return result; } - public static IntervalList? Merge(IntervalList? x, IntervalList? y) - { - if (x is null) - { - return y; - } - if (y is null) - { - return x; - } - - if (x.Start > y.Start) - { - (x, y) = (y, x); - } - - Debug.Assert(x.End != y.Start); - - // We now have x, y non-null and x.End < y.Start. - - if (y.Start == x.End + 1) - { - // The two intervals adjoin. Merge them into one and then - // merge the tails. - x.End = y.End; - x.Next = Merge(x.Next, y.Next); - return x; - } - - // y belongs in the tail of x. - - x.Next = Merge(y, x.Next); - return x; - } + return -1; } } }