From b1b1b44e0f81ca16a9f4e331ad0ff353931af950 Mon Sep 17 00:00:00 2001 From: henry Date: Fri, 16 May 2025 21:21:07 -0400 Subject: [PATCH] wip - trying to move expensive alculation to background --- AccessQueuePlayground/Program.cs | 13 +++++++ AccessQueuePlayground/appsettings.json | 11 +++--- .../Data/TakeANumberAccessQueueRepo.cs | 38 ++++++++++++++++++- AccessQueueService/Program.cs | 13 +++++++ AccessQueueService/appsettings.json | 3 +- 5 files changed, 71 insertions(+), 7 deletions(-) diff --git a/AccessQueuePlayground/Program.cs b/AccessQueuePlayground/Program.cs index f00ab40..b99ef23 100644 --- a/AccessQueuePlayground/Program.cs +++ b/AccessQueuePlayground/Program.cs @@ -26,6 +26,19 @@ builder.Services.AddHostedService(); var app = builder.Build(); +// Configure TakeANumberAccessQueueRepo active users cache +using (var scope = app.Services.CreateScope()) +{ + var config = scope.ServiceProvider.GetRequiredService(); + var repo = scope.ServiceProvider.GetRequiredService() as TakeANumberAccessQueueRepo; + if (repo != null) + { + int activeSeconds = config.GetValue("AccessQueue:ActivitySeconds", 2); + int updateInterval = config.GetValue("AccessQueue:ActiveUsersUpdateIntervalSeconds", 2); + repo.ConfigureActiveUsersCache(activeSeconds, updateInterval); + } +} + // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { diff --git a/AccessQueuePlayground/appsettings.json b/AccessQueuePlayground/appsettings.json index 9cfd1aa..13e15fe 100644 --- a/AccessQueuePlayground/appsettings.json +++ b/AccessQueuePlayground/appsettings.json @@ -6,13 +6,14 @@ } }, "AccessQueue": { - "CapacityLimit": 3, // Maximum number of active users - "ActivitySeconds": 2, // Time since last access before a user is considered inactive - "ExpirationSeconds": 10, // 12 hours - Time before a user access is revoked - "RollingExpiration": true // Whether to extend expiration time on access + "CapacityLimit": 3, + "ActivitySeconds": 2, + "ExpirationSeconds": 10, + "RollingExpiration": true, + "ActiveUsersUpdateIntervalSeconds": 2 }, "AccessQueuePlayground": { - "RefreshRateMilliseconds": 200 // How often to re-request access and update the UI + "RefreshRateMilliseconds": 200 }, "AllowedHosts": "*" } diff --git a/AccessQueueService/Data/TakeANumberAccessQueueRepo.cs b/AccessQueueService/Data/TakeANumberAccessQueueRepo.cs index f05c660..8394469 100644 --- a/AccessQueueService/Data/TakeANumberAccessQueueRepo.cs +++ b/AccessQueueService/Data/TakeANumberAccessQueueRepo.cs @@ -1,5 +1,6 @@ using AccessQueueService.Models; using Microsoft.Extensions.Configuration; +using System.Threading; namespace AccessQueueService.Data { @@ -12,6 +13,12 @@ namespace AccessQueueService.Data private ulong _nowServing = 0; private ulong _nextUnusedTicket = 0; + private int _cachedActiveUsers = 0; + private int _activeSeconds = 60; // default, can be set from config + private Timer? _activeUsersUpdateTimer; + private int _activeUsersUpdateIntervalSeconds = 10; // default, can be set from config + private readonly object _activeUsersLock = new(); + public int GetUnexpiredTicketsCount() => _accessTickets.Count(t => t.Value.ExpiresOn > DateTime.UtcNow); public int GetActiveTicketsCount(DateTime activeCutoff) => _accessTickets .Count(t => t.Value.ExpiresOn > DateTime.UtcNow && t.Value.LastActive > activeCutoff); @@ -54,7 +61,8 @@ namespace AccessQueueService.Data { var now = DateTime.UtcNow; var activeCutoff = now.AddSeconds(-activeSeconds); - var numberOfActiveUsers = _accessTickets.Count(t => t.Value.ExpiresOn > now && t.Value.LastActive > activeCutoff); + // Use cached value instead of recalculating + var numberOfActiveUsers = GetCachedActiveUsers(); var openSpots = capacityLimit - numberOfActiveUsers; if(openSpots <= 0) { @@ -104,5 +112,33 @@ namespace AccessQueueService.Data } return _accessTickets.Remove(userId); } + + public void ConfigureActiveUsersCache(int activeSeconds, int updateIntervalSeconds) + { + _activeSeconds = activeSeconds; + _activeUsersUpdateIntervalSeconds = updateIntervalSeconds; + _activeUsersUpdateTimer?.Dispose(); + _activeUsersUpdateTimer = new Timer(_ => UpdateActiveUsersCache(), null, 0, _activeUsersUpdateIntervalSeconds * 1000); + } + + private void UpdateActiveUsersCache() + { + var now = DateTime.UtcNow; + var activeCutoff = now.AddSeconds(-_activeSeconds); + int count = 0; + lock (_activeUsersLock) + { + count = _accessTickets.Count(t => t.Value.ExpiresOn > now && t.Value.LastActive > activeCutoff); + _cachedActiveUsers = count; + } + } + + public int GetCachedActiveUsers() + { + lock (_activeUsersLock) + { + return _cachedActiveUsers; + } + } } } diff --git a/AccessQueueService/Program.cs b/AccessQueueService/Program.cs index 3e6dbd5..91d49a5 100644 --- a/AccessQueueService/Program.cs +++ b/AccessQueueService/Program.cs @@ -22,6 +22,19 @@ builder.Services.AddHostedService(); var app = builder.Build(); +// Configure TakeANumberAccessQueueRepo active users cache +using (var scope = app.Services.CreateScope()) +{ + var config = scope.ServiceProvider.GetRequiredService(); + var repo = scope.ServiceProvider.GetRequiredService() as TakeANumberAccessQueueRepo; + if (repo != null) + { + int activeSeconds = config.GetValue("AccessQueue:ActivitySeconds", 900); + int updateInterval = config.GetValue("AccessQueue:ActiveUsersUpdateIntervalSeconds", 10); + repo.ConfigureActiveUsersCache(activeSeconds, updateInterval); + } +} + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/AccessQueueService/appsettings.json b/AccessQueueService/appsettings.json index 42f1ca9..e5022fc 100644 --- a/AccessQueueService/appsettings.json +++ b/AccessQueueService/appsettings.json @@ -28,7 +28,8 @@ "ActivitySeconds": 900, "ExpirationSeconds": 43200, "RollingExpiration": true, - "CleanupIntervalSeconds": 60 + "CleanupIntervalSeconds": 60, + "ActiveUsersUpdateIntervalSeconds": 10 }, "AllowedHosts": "*" }