|
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 | + |
| 46 | + /// <summary> |
| 47 | + /// A bit is SET/true in _freeSet if the corresponding integer is FREE |
| 48 | + /// A bit is UNSET/false in freeSet if the corresponding integer is ALLOCATED |
| 49 | + /// </summary> |
| 50 | + private readonly BitArray _freeSet; |
| 51 | + |
| 52 | + /// <summary> |
| 53 | + /// Creates an IntAllocator allocating integer IDs within the |
| 54 | + /// inclusive range [<c>bottom</c>, <c>top</c>]. |
| 55 | + /// </summary> |
| 56 | + /// <param name="bottom">lower end of range</param> |
| 57 | + /// <param name="top">upper end of range (incusive)</param> |
| 58 | + /// <exception cref="ArgumentException"></exception> |
| 59 | + public IntAllocator(int bottom, int top) |
55 | 60 | { |
56 | | - if (start > end) |
| 61 | + if (bottom > top) |
57 | 62 | { |
58 | | - throw new ArgumentException($"illegal range [{start}, {end}]"); |
| 63 | + throw new ArgumentException($"illegal range [{bottom}, {top}]"); |
59 | 64 | } |
60 | 65 |
|
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); |
| 66 | + _loRange = bottom; |
| 67 | + _hiRange = top + 1; |
| 68 | + _numberOfBits = _hiRange - _loRange; |
| 69 | + _freeSet = new BitArray(_numberOfBits, true); // All integers are FREE initially |
64 | 70 | } |
65 | 71 |
|
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 | 72 | public int Allocate() |
72 | 73 | { |
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 |
| 74 | + int setIndex = nextSetBit(); |
| 75 | + if (setIndex < 0) // no free integers are available |
91 | 76 | { |
92 | 77 | return -1; |
93 | 78 | } |
| 79 | + _freeSet.Set(setIndex, false); |
| 80 | + return setIndex + _loRange; |
94 | 81 | } |
95 | 82 |
|
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) |
107 | | - { |
108 | | - if (_unsortedCount >= _unsorted.Length) |
109 | | - { |
110 | | - Flush(); |
111 | | - } |
112 | | - _unsorted[_unsortedCount++] = id; |
113 | | - } |
114 | | - |
115 | | - private void Flush() |
| 83 | + /// <summary> |
| 84 | + /// Makes the provided integer available for allocation again. |
| 85 | + /// </summary> |
| 86 | + /// <param name="reservation">the previously allocated integer to free</param> |
| 87 | + public void Free(int reservation) |
116 | 88 | { |
117 | | - if (_unsortedCount > 0) |
118 | | - { |
119 | | - _base = IntervalList.Merge(_base, IntervalList.FromArray(_unsorted, _unsortedCount)); |
120 | | - _unsortedCount = 0; |
121 | | - } |
| 89 | + int setIndex = reservation - _loRange; |
| 90 | + _freeSet.Set(setIndex, true); // true means "unallocated" |
122 | 91 | } |
123 | 92 |
|
124 | | - |
125 | | - public class IntervalList |
| 93 | + /// <summary> |
| 94 | + /// Note: this is different than the Java implementation, because we need to |
| 95 | + /// preserve the prior behavior of always reserving low integers, if available. |
| 96 | + /// See <c>Test.Integration.AllocateAfterFreeingMany</c> |
| 97 | + /// </summary> |
| 98 | + /// <returns>index of the next unallocated bit</returns> |
| 99 | + private int nextSetBit() |
126 | 100 | { |
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) |
| 101 | + for (int i = 0; i < _freeSet.Count; i++) |
134 | 102 | { |
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) |
144 | | - { |
145 | | - Array.Sort(xs, 0, length); |
146 | | - |
147 | | - IntervalList? result = null; |
148 | | - IntervalList? current = null; |
149 | | - |
150 | | - int i = 0; |
151 | | - while (i < length) |
| 103 | + if (_freeSet.Get(i)) // true means "unallocated" |
152 | 104 | { |
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++; |
| 105 | + return i; |
172 | 106 | } |
173 | | - return result; |
174 | 107 | } |
175 | 108 |
|
176 | | - public static IntervalList? Merge(IntervalList? x, IntervalList? y) |
177 | | - { |
178 | | - if (x is null) |
179 | | - { |
180 | | - return y; |
181 | | - } |
182 | | - if (y is null) |
183 | | - { |
184 | | - return x; |
185 | | - } |
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 | | - } |
| 109 | + return -1; |
210 | 110 | } |
211 | 111 | } |
212 | 112 | } |
0 commit comments