From fb324e7576a72ca573984a6a21f12eab84bd8148 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 4 Sep 2024 01:46:39 +0200 Subject: [PATCH] refactor: replace periodic tasks loop with background service --- .../Extensions/WebApplicationExtensions.cs | 57 ++++++++++++------- Foxnouns.Backend/Program.cs | 37 ++---------- Foxnouns.Backend/Services/KeyCacheService.cs | 4 +- .../Services/PeriodicTasksService.cs | 26 +++++++++ 4 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 Foxnouns.Backend/Services/PeriodicTasksService.cs diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs index a76928c..bf6eda2 100644 --- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs +++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs @@ -5,6 +5,7 @@ using Foxnouns.Backend.Jobs; using Foxnouns.Backend.Middleware; using Foxnouns.Backend.Services; using Microsoft.EntityFrameworkCore; +using Minio; using NodaTime; using Prometheus; using Serilog; @@ -57,16 +58,6 @@ public static class WebApplicationExtensions return config; } - public static WebApplicationBuilder AddMetrics(this WebApplicationBuilder builder, Config config) - { - builder.Services.AddMetricServer(o => o.Port = config.Logging.MetricsPort) - .AddSingleton(); - if (!config.Logging.EnableMetrics) - builder.Services.AddHostedService(); - - return builder; - } - public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder builder) { var file = Environment.GetEnvironmentVariable("FOXNOUNS_CONFIG_FILE") ?? "config.ini"; @@ -78,18 +69,40 @@ public static class WebApplicationExtensions .AddEnvironmentVariables(); } - public static IServiceCollection AddCustomServices(this IServiceCollection services) => services - .AddSingleton(SystemClock.Instance) - .AddSnowflakeGenerator() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - // Transient jobs - .AddTransient() - .AddTransient(); + /// + /// Adds required services to the IServiceCollection. + /// This should only add services that are not ASP.NET-related (i.e. no middleware). + /// + public static IServiceCollection AddServices(this IServiceCollection services, Config config) + { + services + .AddQueue() + .AddDbContext() + .AddMetricServer(o => o.Port = config.Logging.MetricsPort) + .AddMinio(c => + c.WithEndpoint(config.Storage.Endpoint) + .WithCredentials(config.Storage.AccessKey, config.Storage.SecretKey) + .Build()) + .AddSingleton() + .AddSingleton(SystemClock.Instance) + .AddSnowflakeGenerator() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + // Background services + .AddHostedService() + // Transient jobs + .AddTransient() + .AddTransient(); + + if (!config.Logging.EnableMetrics) + services.AddHostedService(); + + return services; + } public static IServiceCollection AddCustomMiddleware(this IServiceCollection services) => services .AddScoped() diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs index e849bd1..911c895 100644 --- a/Foxnouns.Backend/Program.cs +++ b/Foxnouns.Backend/Program.cs @@ -1,12 +1,9 @@ -using Coravel; using Foxnouns.Backend; -using Foxnouns.Backend.Database; using Serilog; using Foxnouns.Backend.Extensions; using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; -using Minio; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Prometheus; @@ -19,9 +16,7 @@ var builder = WebApplication.CreateBuilder(args); var config = builder.AddConfiguration(); -builder - .AddSerilog() - .AddMetrics(config); +builder.AddSerilog(); builder.WebHost .UseSentry(opts => @@ -64,16 +59,10 @@ JsonConvert.DefaultSettings = () => new JsonSerializerSettings }; builder.Services - .AddQueue() - .AddDbContext() - .AddCustomServices() + .AddServices(config) .AddCustomMiddleware() .AddEndpointsApiExplorer() - .AddSwaggerGen() - .AddMinio(c => - c.WithEndpoint(config.Storage.Endpoint) - .WithCredentials(config.Storage.AccessKey, config.Storage.SecretKey) - .Build()); + .AddSwaggerGen(); var app = builder.Build(); @@ -97,23 +86,5 @@ app.Urls.Add(config.Address); Metrics.DefaultRegistry.AddBeforeCollectCallback(async ct => await app.Services.GetRequiredService().CollectMetricsAsync(ct)); -// Fire off the periodic tasks loop in the background -_ = new Timer(_ => -{ - var __ = RunPeriodicTasksAsync(); -}, null, TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1)); - app.Run(); -Log.CloseAndFlush(); - -return; - -async Task RunPeriodicTasksAsync() -{ - await using var scope = app.Services.CreateAsyncScope(); - var logger = scope.ServiceProvider.GetRequiredService(); - logger.Debug("Running periodic tasks"); - - var keyCacheSvc = scope.ServiceProvider.GetRequiredService(); - await keyCacheSvc.DeleteExpiredKeysAsync(); -} \ No newline at end of file +Log.CloseAndFlush(); \ No newline at end of file diff --git a/Foxnouns.Backend/Services/KeyCacheService.cs b/Foxnouns.Backend/Services/KeyCacheService.cs index 4523d16..108199c 100644 --- a/Foxnouns.Backend/Services/KeyCacheService.cs +++ b/Foxnouns.Backend/Services/KeyCacheService.cs @@ -37,9 +37,9 @@ public class KeyCacheService(DatabaseContext db, IClock clock, ILogger logger) return value.Value; } - public async Task DeleteExpiredKeysAsync() + public async Task DeleteExpiredKeysAsync(CancellationToken ct) { - var count = await db.TemporaryKeys.Where(k => k.Expires < clock.GetCurrentInstant()).ExecuteDeleteAsync(); + var count = await db.TemporaryKeys.Where(k => k.Expires < clock.GetCurrentInstant()).ExecuteDeleteAsync(ct); if (count != 0) logger.Information("Removed {Count} expired keys from the database", count); } diff --git a/Foxnouns.Backend/Services/PeriodicTasksService.cs b/Foxnouns.Backend/Services/PeriodicTasksService.cs new file mode 100644 index 0000000..d043317 --- /dev/null +++ b/Foxnouns.Backend/Services/PeriodicTasksService.cs @@ -0,0 +1,26 @@ +namespace Foxnouns.Backend.Services; + +public class PeriodicTasksService(ILogger logger, IServiceProvider services) : BackgroundService +{ + private readonly ILogger _logger = logger.ForContext(); + + protected override async Task ExecuteAsync(CancellationToken ct) + { + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + while (await timer.WaitForNextTickAsync(ct)) + { + _logger.Debug("Collecting metrics"); + await RunPeriodicTasksAsync(ct); + } + } + + private async Task RunPeriodicTasksAsync(CancellationToken ct) + { + _logger.Debug("Running periodic tasks"); + + await using var scope = services.CreateAsyncScope(); + + var keyCacheSvc = scope.ServiceProvider.GetRequiredService(); + await keyCacheSvc.DeleteExpiredKeysAsync(ct); + } +} \ No newline at end of file