/// <summary> /// Gets the distributed context or creates a new one if the default tenant has changed. /// </summary> private async Task <DistributedContext> GetOrCreateDistributedContextAsync(ShellContext defaultContext) { // Check if the default tenant has changed. if (_defaultContext != defaultContext) { _defaultContext = defaultContext; var previousContext = _context; // Create a new distributed context based on the default tenant settings. _context = await CreateDistributedContextAsync(defaultContext.Settings); // Release the previous one. previousContext?.Release(); } return(_context); }
/// <summary> /// Keep in sync tenants by sharing shell identifiers through an <see cref="IDistributedCache"/>. /// </summary> protected override async Task ExecuteAsync(CancellationToken stoppingToken) { stoppingToken.Register(() => { _logger.LogInformation("'{ServiceName}' is stopping.", nameof(DistributedShellHostedService)); }); // Init the idle time. var idleTime = MinIdleTime; while (!stoppingToken.IsCancellationRequested) { try { // Wait for the current idle time on each loop. if (!await TryWaitAsync(idleTime, stoppingToken)) { break; } // If there is no default tenant or it is not running, nothing to do. if (!_shellHost.TryGetShellContext(ShellHelper.DefaultShellName, out var defaultContext) || defaultContext.Settings.State != TenantState.Running) { continue; } // Get or create a new distributed context if the default tenant has changed. var context = await GetOrCreateDistributedContextAsync(defaultContext); // If the required distributed features are not enabled, nothing to do. var distributedCache = context?.DistributedCache; if (distributedCache == null) { continue; } // Try to retrieve the tenant changed global identifier from the distributed cache. string shellChangedId; try { shellChangedId = await distributedCache.GetStringAsync(ShellChangedIdKey); } catch (Exception ex) when(!ex.IsFatal()) { // Get the next idle time before retrying to read the distributed cache. idleTime = NextIdleTimeBeforeRetry(idleTime, ex); continue; } // Reset the idle time. idleTime = MinIdleTime; // Check if at least one tenant has changed. if (shellChangedId == null || _shellChangedId == shellChangedId) { continue; } // Try to retrieve the tenant created global identifier from the distributed cache. string shellCreatedId; try { shellCreatedId = await distributedCache.GetStringAsync(ShellCreatedIdKey); } catch (Exception ex) when(!ex.IsFatal()) { _logger.LogError(ex, "Unable to read the distributed cache before checking if a tenant has been created."); continue; } // Retrieve all tenant settings that are already loaded. var allSettings = _shellHost.GetAllSettings().ToList(); // Check if at least one tenant has been created. if (shellCreatedId != null && _shellCreatedId != shellCreatedId) { // Retrieve all new created tenants that are not already loaded. var names = (await _shellSettingsManager.LoadSettingsNamesAsync()) .Except(allSettings.Select(s => s.Name)) .ToArray(); // Load and enlist the settings of all new created tenant. foreach (var name in names) { allSettings.Add(await _shellSettingsManager.LoadSettingsAsync(name)); } } // Init the busy start time. var _busyStartTime = DateTime.UtcNow; var syncingSuccess = true; // Keep in sync all tenants by checking their specific identifiers. foreach (var settings in allSettings) { // Wait for the min idle time after the max busy time. if (!await TryWaitAfterBusyTime(stoppingToken)) { break; } var semaphore = _semaphores.GetOrAdd(settings.Name, name => new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { // Try to retrieve the release identifier of this tenant from the distributed cache. var releaseId = await distributedCache.GetStringAsync(ReleaseIdKey(settings.Name)); if (releaseId != null) { // Check if the release identifier of this tenant has changed. var identifier = _identifiers.GetOrAdd(settings.Name, name => new ShellIdentifier()); if (identifier.ReleaseId != releaseId) { // Upate the local identifier. identifier.ReleaseId = releaseId; // Keep in sync this tenant by releasing it locally. await _shellHost.ReleaseShellContextAsync(settings, eventSource : false); } } // Try to retrieve the reload identifier of this tenant from the distributed cache. var reloadId = await distributedCache.GetStringAsync(ReloadIdKey(settings.Name)); if (reloadId != null) { // Check if the reload identifier of this tenant has changed. var identifier = _identifiers.GetOrAdd(settings.Name, name => new ShellIdentifier()); if (identifier.ReloadId != reloadId) { // Upate the local identifier. identifier.ReloadId = reloadId; // Keep in sync this tenant by reloading it locally. await _shellHost.ReloadShellContextAsync(settings, eventSource : false); } } } catch (Exception ex) when(!ex.IsFatal()) { syncingSuccess = false; _logger.LogError(ex, "Unable to read the distributed cache while syncing the tenant '{TenantName}'.", settings.Name); break; } finally { semaphore.Release(); } } // Keep in sync the tenant global identifiers. if (syncingSuccess) { _shellChangedId = shellChangedId; _shellCreatedId = shellCreatedId; } } catch (Exception ex) when(!ex.IsFatal()) { _logger.LogError(ex, "Error while executing '{ServiceName}'", nameof(DistributedShellHostedService)); } } _terminated = true; _context?.Release(); _defaultContext = null; _context = null; }