|  | 
| 30 | 30 | //--------------------------------------------------------------------------- | 
| 31 | 31 | 
 | 
| 32 | 32 | using System; | 
| 33 |  | -using System.Diagnostics; | 
|  | 33 | +using System.Collections; | 
| 34 | 34 | 
 | 
| 35 | 35 | namespace RabbitMQ.Client.Util | 
| 36 | 36 | { | 
| 37 |  | -    /** | 
| 38 |  | -   * A class for allocating integer IDs in a given range. | 
| 39 |  | -   */ | 
|  | 37 | +    /// <summary> | 
|  | 38 | +    /// <see href="https://github.com/rabbitmq/rabbitmq-java-client/blob/main/src/main/java/com/rabbitmq/utility/IntAllocator.java"/> | 
|  | 39 | +    /// </summary> | 
| 40 | 40 |     internal class IntAllocator | 
| 41 | 41 |     { | 
| 42 |  | -        private readonly int[] _unsorted; | 
| 43 |  | -        private IntervalList? _base; | 
| 44 |  | -        private int _unsortedCount = 0; | 
| 45 |  | - | 
| 46 |  | -        /** | 
| 47 |  | -     * A class representing a list of inclusive intervals | 
| 48 |  | -     */ | 
| 49 |  | - | 
| 50 |  | -        /** | 
| 51 |  | -     * Creates an IntAllocator allocating integer IDs within the inclusive range [start, end] | 
| 52 |  | -     */ | 
| 53 |  | - | 
| 54 |  | -        public IntAllocator(int start, int end) | 
|  | 42 | +        private readonly int _loRange; // the integer that bit 0 represents | 
|  | 43 | +        private readonly int _hiRange; // one more than the integer the highest bit represents | 
|  | 44 | +        private readonly int _numberOfBits; // | 
|  | 45 | +        private int _lastIndex = 0; // for searching for FREE integers | 
|  | 46 | + | 
|  | 47 | +        /// <summary> | 
|  | 48 | +        /// A bit is SET/true in _freeSet if the corresponding integer is FREE | 
|  | 49 | +        /// A bit is UNSET/false in freeSet if the corresponding integer is ALLOCATED | 
|  | 50 | +        /// </summary> | 
|  | 51 | +        private readonly BitArray _freeSet; | 
|  | 52 | + | 
|  | 53 | +        /// <summary> | 
|  | 54 | +        /// Creates an IntAllocator allocating integer IDs within the | 
|  | 55 | +        /// inclusive range [<c>bottom</c>, <c>top</c>]. | 
|  | 56 | +        /// </summary> | 
|  | 57 | +        /// <param name="bottom">lower end of range</param> | 
|  | 58 | +        /// <param name="top">upper end of range (incusive)</param> | 
|  | 59 | +        /// <exception cref="ArgumentException"></exception> | 
|  | 60 | +        public IntAllocator(int bottom, int top) | 
| 55 | 61 |         { | 
| 56 |  | -            if (start > end) | 
|  | 62 | +            if (bottom > top) | 
| 57 | 63 |             { | 
| 58 |  | -                throw new ArgumentException($"illegal range [{start}, {end}]"); | 
|  | 64 | +                throw new ArgumentException($"illegal range [{bottom}, {top}]"); | 
| 59 | 65 |             } | 
| 60 | 66 | 
 | 
| 61 |  | -            // Fairly arbitrary heuristic for a good size for the unsorted set. | 
| 62 |  | -            _unsorted = new int[Math.Max(32, (int)Math.Sqrt(end - start))]; | 
| 63 |  | -            _base = new IntervalList(start, end); | 
|  | 67 | +            _loRange = bottom; | 
|  | 68 | +            _hiRange = top + 1; | 
|  | 69 | +            _numberOfBits = _hiRange - _loRange; | 
|  | 70 | +            _freeSet = new BitArray(_numberOfBits, true); // All integers are FREE initially | 
| 64 | 71 |         } | 
| 65 | 72 | 
 | 
| 66 |  | -        /** | 
| 67 |  | -     * Allocate a fresh integer from the range, or return -1 if no more integers | 
| 68 |  | -     * are available. This operation is guaranteed to run in O(1) | 
| 69 |  | -     */ | 
| 70 |  | - | 
| 71 | 73 |         public int Allocate() | 
| 72 | 74 |         { | 
| 73 |  | -            if (_unsortedCount > 0) | 
| 74 |  | -            { | 
| 75 |  | -                return _unsorted[--_unsortedCount]; | 
| 76 |  | -            } | 
| 77 |  | -            else if (_base != null) | 
| 78 |  | -            { | 
| 79 |  | -                int result = _base.Start; | 
| 80 |  | -                if (_base.Start == _base.End) | 
| 81 |  | -                { | 
| 82 |  | -                    _base = _base.Next; | 
| 83 |  | -                } | 
| 84 |  | -                else | 
| 85 |  | -                { | 
| 86 |  | -                    _base.Start++; | 
| 87 |  | -                } | 
| 88 |  | -                return result; | 
| 89 |  | -            } | 
| 90 |  | -            else | 
|  | 75 | +            int setIndex = nextSetBit(); | 
|  | 76 | +            if (setIndex < 0) // no free integers are available | 
| 91 | 77 |             { | 
| 92 | 78 |                 return -1; | 
| 93 | 79 |             } | 
|  | 80 | +            _lastIndex = setIndex; | 
|  | 81 | +            _freeSet.Set(setIndex, false); | 
|  | 82 | +            return setIndex + _loRange; | 
| 94 | 83 |         } | 
| 95 | 84 | 
 | 
| 96 |  | -        /** | 
| 97 |  | -     * Make the provided integer available for allocation again. This operation | 
| 98 |  | -     * runs in amortized O(sqrt(range size)) time: About every sqrt(range size) | 
| 99 |  | -     * operations  will take O(range_size + number of intervals) to complete and | 
| 100 |  | -     * the rest run in constant time. | 
| 101 |  | -     * | 
| 102 |  | -     * No error checking is performed, so if you double Free or Free an integer | 
| 103 |  | -     * that was not originally Allocated the results are undefined. Sorry. | 
| 104 |  | -     */ | 
| 105 |  | - | 
| 106 |  | -        public void Free(int id) | 
|  | 85 | +        /// <summary> | 
|  | 86 | +        /// Makes the provided integer available for allocation again. | 
|  | 87 | +        /// </summary> | 
|  | 88 | +        /// <param name="reservation">the previously allocated integer to free</param> | 
|  | 89 | +        public void Free(int reservation) | 
| 107 | 90 |         { | 
| 108 |  | -            if (_unsortedCount >= _unsorted.Length) | 
| 109 |  | -            { | 
| 110 |  | -                Flush(); | 
| 111 |  | -            } | 
| 112 |  | -            _unsorted[_unsortedCount++] = id; | 
|  | 91 | +            int setIndex = reservation - _loRange; | 
|  | 92 | +            _freeSet.Set(setIndex, true); // true means "unallocated" | 
| 113 | 93 |         } | 
| 114 | 94 | 
 | 
| 115 |  | -        private void Flush() | 
|  | 95 | +        private int nextSetBit() | 
| 116 | 96 |         { | 
| 117 |  | -            if (_unsortedCount > 0) | 
| 118 |  | -            { | 
| 119 |  | -                _base = IntervalList.Merge(_base, IntervalList.FromArray(_unsorted, _unsortedCount)); | 
| 120 |  | -                _unsortedCount = 0; | 
| 121 |  | -            } | 
| 122 |  | -        } | 
| 123 |  | - | 
| 124 |  | - | 
| 125 |  | -        public class IntervalList | 
| 126 |  | -        { | 
| 127 |  | -            public int End; | 
| 128 |  | - | 
| 129 |  | -            // Invariant: If Next != Null then Next.Start > this.End + 1 | 
| 130 |  | -            public IntervalList? Next; | 
| 131 |  | -            public int Start; | 
| 132 |  | - | 
| 133 |  | -            public IntervalList(int start, int end) | 
| 134 |  | -            { | 
| 135 |  | -                Start = start; | 
| 136 |  | -                End = end; | 
| 137 |  | -            } | 
| 138 |  | - | 
| 139 |  | -            // Destructively merge two IntervalLists. | 
| 140 |  | -            // Invariant: None of the Intervals in the two lists may overlap | 
| 141 |  | -            // intervals in this list. | 
| 142 |  | - | 
| 143 |  | -            public static IntervalList? FromArray(int[] xs, int length) | 
|  | 97 | +            for (int i = _lastIndex; i < _freeSet.Count; i++) | 
| 144 | 98 |             { | 
| 145 |  | -                Array.Sort(xs, 0, length); | 
| 146 |  | - | 
| 147 |  | -                IntervalList? result = null; | 
| 148 |  | -                IntervalList? current = null; | 
| 149 |  | - | 
| 150 |  | -                int i = 0; | 
| 151 |  | -                while (i < length) | 
|  | 99 | +                if (_freeSet.Get(i)) // true means "unallocated" | 
| 152 | 100 |                 { | 
| 153 |  | -                    int start = i; | 
| 154 |  | -                    while ((i < length - 1) && (xs[i + 1] == xs[i] + 1)) | 
| 155 |  | -                    { | 
| 156 |  | -                        i++; | 
| 157 |  | -                    } | 
| 158 |  | - | 
| 159 |  | -                    var interval = new IntervalList(xs[start], xs[i]); | 
| 160 |  | - | 
| 161 |  | -                    if (result is null) | 
| 162 |  | -                    { | 
| 163 |  | -                        result = interval; | 
| 164 |  | -                        current = interval; | 
| 165 |  | -                    } | 
| 166 |  | -                    else | 
| 167 |  | -                    { | 
| 168 |  | -                        current!.Next = interval; | 
| 169 |  | -                        current = interval; | 
| 170 |  | -                    } | 
| 171 |  | -                    i++; | 
|  | 101 | +                    return i; | 
| 172 | 102 |                 } | 
| 173 |  | -                return result; | 
| 174 | 103 |             } | 
| 175 | 104 | 
 | 
| 176 |  | -            public static IntervalList? Merge(IntervalList? x, IntervalList? y) | 
|  | 105 | +            for (int i = 0; i < _lastIndex; i++) | 
| 177 | 106 |             { | 
| 178 |  | -                if (x is null) | 
| 179 |  | -                { | 
| 180 |  | -                    return y; | 
| 181 |  | -                } | 
| 182 |  | -                if (y is null) | 
|  | 107 | +                if (_freeSet.Get(i)) // true means "unallocated" | 
| 183 | 108 |                 { | 
| 184 |  | -                    return x; | 
|  | 109 | +                    return i; | 
| 185 | 110 |                 } | 
| 186 |  | - | 
| 187 |  | -                if (x.Start > y.Start) | 
| 188 |  | -                { | 
| 189 |  | -                    (x, y) = (y, x); | 
| 190 |  | -                } | 
| 191 |  | - | 
| 192 |  | -                Debug.Assert(x.End != y.Start); | 
| 193 |  | - | 
| 194 |  | -                // We now have x, y non-null and x.End < y.Start. | 
| 195 |  | - | 
| 196 |  | -                if (y.Start == x.End + 1) | 
| 197 |  | -                { | 
| 198 |  | -                    // The two intervals adjoin. Merge them into one and then | 
| 199 |  | -                    // merge the tails. | 
| 200 |  | -                    x.End = y.End; | 
| 201 |  | -                    x.Next = Merge(x.Next, y.Next); | 
| 202 |  | -                    return x; | 
| 203 |  | -                } | 
| 204 |  | - | 
| 205 |  | -                // y belongs in the tail of x. | 
| 206 |  | - | 
| 207 |  | -                x.Next = Merge(y, x.Next); | 
| 208 |  | -                return x; | 
| 209 | 111 |             } | 
|  | 112 | + | 
|  | 113 | +            return -1; | 
| 210 | 114 |         } | 
| 211 | 115 |     } | 
| 212 | 116 | } | 
0 commit comments