Skip to content

Commit 4b866e1

Browse files
committed
Port IntAllocator from rabbitmq-java-client
Fixes #1786
1 parent eb64b79 commit 4b866e1

File tree

1 file changed

+50
-150
lines changed

1 file changed

+50
-150
lines changed

projects/RabbitMQ.Client/Util/IntAllocator.cs

Lines changed: 50 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -30,183 +30,83 @@
3030
//---------------------------------------------------------------------------
3131

3232
using System;
33-
using System.Diagnostics;
33+
using System.Collections;
3434

3535
namespace RabbitMQ.Client.Util
3636
{
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>
4040
internal class IntAllocator
4141
{
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)
5560
{
56-
if (start > end)
61+
if (bottom > top)
5762
{
58-
throw new ArgumentException($"illegal range [{start}, {end}]");
63+
throw new ArgumentException($"illegal range [{bottom}, {top}]");
5964
}
6065

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
6470
}
6571

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-
7172
public int Allocate()
7273
{
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
9176
{
9277
return -1;
9378
}
79+
_freeSet.Set(setIndex, false);
80+
return setIndex + _loRange;
9481
}
9582

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)
11688
{
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"
12291
}
12392

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()
126100
{
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++)
134102
{
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"
152104
{
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;
172106
}
173-
return result;
174107
}
175108

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;
210110
}
211111
}
212112
}

0 commit comments

Comments
 (0)