From 91799f778b9b544575837b85f3eba27728218e1f Mon Sep 17 00:00:00 2001 From: Vaughan Knight Date: Wed, 19 Jun 2024 11:10:05 +1000 Subject: [PATCH] Fixed a bug where location sources were loading twice Fixed a bug where location sources were loading twice. Added a semaphore to stop any threading issues, and also stopped it loading twice in the service configuration. --- .../src/LocationSource.cs | 43 ++++++++++++++----- .../ServiceCollectionExtensions.cs | 22 ++++++++-- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/CarbonAware.LocationSources/src/LocationSource.cs b/src/CarbonAware.LocationSources/src/LocationSource.cs index 793b09dd8..96eb5e5a7 100644 --- a/src/CarbonAware.LocationSources/src/LocationSource.cs +++ b/src/CarbonAware.LocationSources/src/LocationSource.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using System.Reflection; using System.Text.Json; +using System.Threading; namespace CarbonAware.LocationSources; @@ -22,7 +23,6 @@ internal class LocationSource : ILocationSource private LocationDataSourcesConfiguration _configuration => _configurationMonitor.CurrentValue; - /// /// Creates a new instance of the class. /// @@ -61,14 +61,21 @@ private async Task LoadLocationJsonFileAsync() var sourceFiles = !_configuration.LocationSourceFiles.Any() ? DiscoverFiles() : _configuration.LocationSourceFiles; foreach (var source in sourceFiles) { - using Stream stream = GetStreamFromFileLocation(source); - var namedGeoMap = await JsonSerializer.DeserializeAsync>(stream, options); - foreach (var locationKey in namedGeoMap!.Keys) + if (File.Exists(source.DataFileLocation!)) + { + using Stream stream = GetStreamFromFileLocation(source); + var namedGeoMap = await JsonSerializer.DeserializeAsync>(stream, options); + foreach (var locationKey in namedGeoMap!.Keys) + { + var geoInstance = namedGeoMap[locationKey]; + geoInstance.AssertValid(); + var key = BuildKey(source, locationKey); + AddToLocationMap(key, geoInstance, source.DataFileLocation, keyCounter); + } + } + else { - var geoInstance = namedGeoMap[locationKey]; - geoInstance.AssertValid(); - var key = BuildKey(source, locationKey); - AddToLocationMap(key, geoInstance, source.DataFileLocation, keyCounter); + _logger.LogError($"Configured data source not found at '{source.DataFileLocation}'"); } } } @@ -78,11 +85,27 @@ private String BuildKey(LocationSourceFile locationData, string locationName) return $"{locationData.Prefix}{locationData.Delimiter}{locationName}"; } + /// + /// Semaphore is to stop any concurrent loading of the file, which + /// could in turn update the list twice and result in duplicate entries, + /// in turn resulting in suffixed keys to avoid clashes when not + /// required. + /// + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private async Task LoadLocationFromFileIfNotPresentAsync() { - if (!_allLocations.Any()) + await _semaphore.WaitAsync(); + + try + { + if (!_allLocations.Any()) + { + await LoadLocationJsonFileAsync(); + } + } + finally { - await LoadLocationJsonFileAsync(); + _semaphore.Release(); } } diff --git a/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs b/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs index 440e657b4..ee38dddad 100644 --- a/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs +++ b/src/GSF.CarbonAware/src/Configuration/ServiceCollectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Linq; namespace GSF.CarbonAware.Configuration; @@ -26,21 +27,34 @@ private static IServiceCollection ConfigureLocationDataSourcesConfiguration(thi /// public static IServiceCollection AddEmissionsServices(this IServiceCollection services, IConfiguration configuration) { - services.ConfigureLocationDataSourcesConfiguration(configuration); - services.TryAddSingleton(); + AddLocationServices(services, configuration); services.AddDataSourceService(configuration); services.TryAddSingleton(); services.TryAddSingleton(); return services; } + /// + /// This stops the location configuration being loaded twice if needed for + /// historical emissions and forecasted emissions services. + /// + /// + /// + private static void AddLocationServices(IServiceCollection services, IConfiguration configuration) + { + if (!services.Any(x => x.ServiceType == typeof(ILocationSource))) + { + services.ConfigureLocationDataSourcesConfiguration(configuration); + services.TryAddSingleton(); + } + } + /// /// Add services needed in order to use an Forecast service. /// public static IServiceCollection AddForecastServices(this IServiceCollection services, IConfiguration configuration) { - services.ConfigureLocationDataSourcesConfiguration(configuration); - services.TryAddSingleton(); + AddLocationServices(services, configuration); services.AddDataSourceService(configuration); services.TryAddSingleton(); services.TryAddSingleton();