Skip to content

Commit 1fa7402

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

File tree

2 files changed

+51
-147
lines changed

2 files changed

+51
-147
lines changed

projects/RabbitMQ.Client/Util/IntAllocator.cs

Lines changed: 50 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -30,183 +30,87 @@
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+
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)
5561
{
56-
if (start > end)
62+
if (bottom > top)
5763
{
58-
throw new ArgumentException($"illegal range [{start}, {end}]");
64+
throw new ArgumentException($"illegal range [{bottom}, {top}]");
5965
}
6066

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
6471
}
6572

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-
7173
public int Allocate()
7274
{
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
9177
{
9278
return -1;
9379
}
80+
_lastIndex = setIndex;
81+
_freeSet.Set(setIndex, false);
82+
return setIndex + _loRange;
9483
}
9584

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)
10790
{
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"
11393
}
11494

115-
private void Flush()
95+
private int nextSetBit()
11696
{
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++)
14498
{
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"
152100
{
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;
172102
}
173-
return result;
174103
}
175104

176-
public static IntervalList? Merge(IntervalList? x, IntervalList? y)
105+
for (int i = 0; i < _lastIndex; i++)
177106
{
178-
if (x is null)
179-
{
180-
return y;
181-
}
182-
if (y is null)
107+
if (_freeSet.Get(i)) // true means "unallocated"
183108
{
184-
return x;
109+
return i;
185110
}
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;
209111
}
112+
113+
return -1;
210114
}
211115
}
212116
}

projects/Test/Integration/TestChannelAllocation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public async Task AllocateAfterFreeingMany()
135135
// already be sorted, but we don't want to force that behaviour
136136
channels.Sort(CompareChannels);
137137

138-
int k = 1;
138+
int k = CHANNEL_COUNT;
139139
foreach (IChannel channel in channels)
140140
{
141141
Assert.Equal(k++, ChannelNumber(channel));

0 commit comments

Comments
 (0)