|
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