using Microsoft.Extensions.Options; using Tiku.Application.Security; using Tiku.Application.Tenancy; namespace Tiku.Api.Background; internal sealed class DevelopmentTenantDomainLifecycleHostedService( IServiceScopeFactory scopeFactory, IOptions domainOptions, ILogger logger) : BackgroundService { private readonly DomainLifecycleOptions options = domainOptions.Value; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (!options.EnableDevelopmentLocalhostBypass) return; var activeInterval = TimeSpan.FromSeconds(Math.Clamp(options.PollSeconds, 1, 3600)); var maxIdleInterval = TimeSpan.FromSeconds(Math.Clamp( options.MaxIdlePollSeconds, (int)activeInterval.TotalSeconds, 3600)); var consecutiveIdleIterations = 0; while (!stoppingToken.IsCancellationRequested) { try { await using var scope = scopeFactory.CreateAsyncScope(); scope.ServiceProvider.GetRequiredService() .InitializeSystem(null, "Development .localhost domain lifecycle"); var processed = await scope.ServiceProvider .GetRequiredService() .ProcessPendingAsync(stoppingToken); consecutiveIdleIterations = processed == 0 ? consecutiveIdleIterations + 1 : 0; if (processed > 0) logger.LogInformation("Processed {DomainCount} pending Development tenant domains.", processed); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } catch (Exception exception) { consecutiveIdleIterations++; logger.LogError(exception, "Development tenant domain lifecycle iteration failed."); } await Task.Delay( CalculateDelay(activeInterval, maxIdleInterval, consecutiveIdleIterations), stoppingToken); } } internal static TimeSpan CalculateDelay( TimeSpan activeInterval, TimeSpan maxIdleInterval, int consecutiveIdleIterations) { if (consecutiveIdleIterations <= 1) return activeInterval; var shift = Math.Min(consecutiveIdleIterations - 1, 20); var delayTicks = activeInterval.Ticks * (1L << shift); return TimeSpan.FromTicks(Math.Min(delayTicks, maxIdleInterval.Ticks)); } }