Fix ulong overflow at max value #29
|
@ -14,4 +14,8 @@
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="AccessQueueServiceTests" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -6,11 +6,11 @@ namespace AccessQueueService.Data
|
||||||
public class TakeANumberAccessQueueRepo : IAccessQueueRepo
|
public class TakeANumberAccessQueueRepo : IAccessQueueRepo
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, AccessTicket> _accessTickets = [];
|
private readonly Dictionary<string, AccessTicket> _accessTickets = [];
|
||||||
private readonly Dictionary<string, ulong> _queueNumbers = [];
|
private Dictionary<string, ulong> _queueNumbers = [];
|
||||||
private readonly Dictionary<ulong, AccessTicket> _accessQueue = [];
|
private Dictionary<ulong, AccessTicket> _accessQueue = [];
|
||||||
|
|
||||||
private ulong _nowServing = 0;
|
internal ulong _nowServing = 0;
|
||||||
private ulong _nextUnusedTicket = 0;
|
internal ulong _nextUnusedTicket = 0;
|
||||||
|
|
||||||
public int GetUnexpiredTicketsCount() => _accessTickets.Count(t => t.Value.ExpiresOn > DateTime.UtcNow);
|
public int GetUnexpiredTicketsCount() => _accessTickets.Count(t => t.Value.ExpiresOn > DateTime.UtcNow);
|
||||||
public int GetActiveTicketsCount(DateTime activeCutoff) => _accessTickets
|
public int GetActiveTicketsCount(DateTime activeCutoff) => _accessTickets
|
||||||
|
@ -32,6 +32,11 @@ namespace AccessQueueService.Data
|
||||||
|
|
||||||
public void Enqueue(AccessTicket ticket)
|
public void Enqueue(AccessTicket ticket)
|
||||||
{
|
{
|
||||||
|
if(_nextUnusedTicket >= long.MaxValue)
|
||||||
|
{
|
||||||
|
// Prevent overflow
|
||||||
|
Optimize();
|
||||||
|
}
|
||||||
_queueNumbers[ticket.UserId] = _nextUnusedTicket;
|
_queueNumbers[ticket.UserId] = _nextUnusedTicket;
|
||||||
_accessQueue[_nextUnusedTicket] = ticket;
|
_accessQueue[_nextUnusedTicket] = ticket;
|
||||||
_nextUnusedTicket++;
|
_nextUnusedTicket++;
|
||||||
|
@ -104,5 +109,22 @@ namespace AccessQueueService.Data
|
||||||
}
|
}
|
||||||
return _accessTickets.Remove(userId);
|
return _accessTickets.Remove(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Optimize()
|
||||||
|
{
|
||||||
|
var newQueue = new Dictionary<ulong, AccessTicket>();
|
||||||
|
var newQueueNumbers = new Dictionary<string, ulong>();
|
||||||
|
ulong newIndex = 0;
|
||||||
|
for (ulong i = _nowServing; i < _nextUnusedTicket; i++)
|
||||||
|
{
|
||||||
|
var user = _accessQueue[i];
|
||||||
|
newQueue[newIndex] = user;
|
||||||
|
newQueueNumbers[user.UserId] = newIndex++;
|
||||||
|
}
|
||||||
|
_accessQueue = newQueue;
|
||||||
|
_queueNumbers = newQueueNumbers;
|
||||||
|
_nowServing = 0;
|
||||||
|
_nextUnusedTicket = newIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ namespace AccessQueueServiceTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetUnexpiredTicketsCount_ReturnsCorrectCount()
|
public void GetUnexpiredTicketsCount_ReturnsCorrectCount()
|
||||||
{
|
{
|
||||||
|
|
||||||
_repo.UpsertTicket(new AccessTicket { UserId = "a", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow });
|
_repo.UpsertTicket(new AccessTicket { UserId = "a", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow });
|
||||||
_repo.UpsertTicket(new AccessTicket { UserId = "b", ExpiresOn = DateTime.UtcNow.AddMinutes(-1), LastActive = DateTime.UtcNow });
|
_repo.UpsertTicket(new AccessTicket { UserId = "b", ExpiresOn = DateTime.UtcNow.AddMinutes(-1), LastActive = DateTime.UtcNow });
|
||||||
Assert.Equal(1, _repo.GetUnexpiredTicketsCount());
|
Assert.Equal(1, _repo.GetUnexpiredTicketsCount());
|
||||||
|
@ -192,5 +191,49 @@ namespace AccessQueueServiceTests
|
||||||
Assert.Null(_repo.GetTicket("second"));
|
Assert.Null(_repo.GetTicket("second"));
|
||||||
Assert.Null(_repo.GetTicket("third"));
|
Assert.Null(_repo.GetTicket("third"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Optimize_MaintainsQueueOrder()
|
||||||
|
{
|
||||||
|
var ticket1 = new AccessTicket { UserId = "first", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
var ticket2 = new AccessTicket { UserId = "second", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
var ticket3 = new AccessTicket { UserId = "third", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
_repo.Enqueue(ticket1);
|
||||||
|
_repo.Enqueue(ticket2);
|
||||||
|
_repo.Enqueue(ticket3);
|
||||||
|
|
||||||
|
_repo.DidDequeueUntilFull(60 * 60, 60, 1);
|
||||||
|
_repo.Optimize();
|
||||||
|
|
||||||
|
Assert.NotNull(_repo.GetTicket("first"));
|
||||||
|
Assert.Equal(0, _repo.GetRequestsAhead("second"));
|
||||||
|
Assert.Equal(1, _repo.GetRequestsAhead("third"));
|
||||||
|
Assert.Equal(0ul, _repo._nowServing);
|
||||||
|
|
||||||
|
_repo.DidDequeueUntilFull(60 * 60, 60, 2);
|
||||||
|
Assert.NotNull(_repo.GetTicket("second"));
|
||||||
|
Assert.Equal(0, _repo.GetRequestsAhead("third"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Enqueue_MaintainsQueueOrder_WhenMaxValueExceeded()
|
||||||
|
{
|
||||||
|
var ticket1 = new AccessTicket { UserId = "first", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
var ticket2 = new AccessTicket { UserId = "second", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
var ticket3 = new AccessTicket { UserId = "third", ExpiresOn = DateTime.UtcNow.AddMinutes(1), LastActive = DateTime.UtcNow };
|
||||||
|
|
||||||
|
_repo._nowServing = long.MaxValue - 1;
|
||||||
|
_repo._nextUnusedTicket = long.MaxValue - 1;
|
||||||
|
|
||||||
|
_repo.Enqueue(ticket1);
|
||||||
|
_repo.Enqueue(ticket2);
|
||||||
|
_repo.Enqueue(ticket3);
|
||||||
|
|
||||||
|
Assert.Equal(0ul, _repo._nowServing);
|
||||||
|
Assert.Equal(3ul, _repo._nextUnusedTicket);
|
||||||
|
|
||||||
|
Assert.Equal(0, _repo.GetRequestsAhead("first"));
|
||||||
|
Assert.Equal(2, _repo.GetRequestsAhead("third"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue