diff --git a/Corgibytes.Freshli.Cli/Functionality/BillOfMaterials/GenerateBillOfMaterialsActivity.cs b/Corgibytes.Freshli.Cli/Functionality/BillOfMaterials/GenerateBillOfMaterialsActivity.cs index c5798fa33..fd5f6da99 100644 --- a/Corgibytes.Freshli.Cli/Functionality/BillOfMaterials/GenerateBillOfMaterialsActivity.cs +++ b/Corgibytes.Freshli.Cli/Functionality/BillOfMaterials/GenerateBillOfMaterialsActivity.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Concurrent; using System.IO; -using Corgibytes.Freshli.Cli.DataModel; +using System.Threading; using Corgibytes.Freshli.Cli.Functionality.Engine; using Corgibytes.Freshli.Cli.Services; using Microsoft.Extensions.DependencyInjection; namespace Corgibytes.Freshli.Cli.Functionality.BillOfMaterials; -public class GenerateBillOfMaterialsActivity : IApplicationActivity +public class GenerateBillOfMaterialsActivity : IApplicationActivity, IMutexed { public readonly string AgentExecutablePath; public readonly Guid AnalysisId; @@ -16,6 +16,7 @@ public class GenerateBillOfMaterialsActivity : IApplicationActivity public readonly string ManifestPath; private static readonly ConcurrentDictionary s_lockPoints = new(); + private static readonly ConcurrentDictionary s_historyPointMutexes = new(); public GenerateBillOfMaterialsActivity(Guid analysisId, string agentExecutablePath, int historyStopPointId, string manifestPath) @@ -26,6 +27,20 @@ public GenerateBillOfMaterialsActivity(Guid analysisId, string agentExecutablePa ManifestPath = manifestPath; } + public Mutex GetMutex(IServiceProvider provider) + { + var cacheManager = provider.GetRequiredService(); + var cacheDb = cacheManager.GetCacheDb(); + + var historyStopPoint = cacheDb.RetrieveHistoryStopPoint(HistoryStopPointId); + // TODO create an exception class for this exception and write a test to cover it getting generated + _ = historyStopPoint ?? throw new Exception($"Failed to retrieve history stop point {HistoryStopPointId}"); + + var historyPointPath = historyStopPoint.LocalPath; + EnsureHistoryPointMutexExists(historyPointPath); + return s_historyPointMutexes[historyPointPath]; + } + public void Handle(IApplicationEventEngine eventClient) { var agentManager = eventClient.ServiceProvider.GetRequiredService(); @@ -40,23 +55,19 @@ public void Handle(IApplicationEventEngine eventClient) var historyPointPath = historyStopPoint.LocalPath; var asOfDateTime = historyStopPoint.AsOfDateTime; - EnsureLockPointExists(historyPointPath); - lock (s_lockPoints[historyPointPath]) - { - var fullManifestPath = Path.Combine(historyPointPath, ManifestPath); - var bomFilePath = agentReader.ProcessManifest(fullManifestPath, asOfDateTime); - var cachedBomFilePath = cacheManager.StoreBomInCache(bomFilePath, AnalysisId, asOfDateTime); + var fullManifestPath = Path.Combine(historyPointPath, ManifestPath); + var bomFilePath = agentReader.ProcessManifest(fullManifestPath, asOfDateTime); + var cachedBomFilePath = cacheManager.StoreBomInCache(bomFilePath, AnalysisId, asOfDateTime); - eventClient.Fire(new BillOfMaterialsGeneratedEvent( - AnalysisId, HistoryStopPointId, cachedBomFilePath, AgentExecutablePath)); - } + eventClient.Fire(new BillOfMaterialsGeneratedEvent( + AnalysisId, HistoryStopPointId, cachedBomFilePath, AgentExecutablePath)); } - private void EnsureLockPointExists(string path) + private static void EnsureHistoryPointMutexExists(string path) { - if (!s_lockPoints.ContainsKey(path)) + if (!s_historyPointMutexes.ContainsKey(path)) { - s_lockPoints[path] = new object(); + s_historyPointMutexes[path] = new Mutex(); } } } diff --git a/Corgibytes.Freshli.Cli/Functionality/Engine/ApplicationEngine.cs b/Corgibytes.Freshli.Cli/Functionality/Engine/ApplicationEngine.cs index c0f5cd243..c6963091e 100644 --- a/Corgibytes.Freshli.Cli/Functionality/Engine/ApplicationEngine.cs +++ b/Corgibytes.Freshli.Cli/Functionality/Engine/ApplicationEngine.cs @@ -133,9 +133,24 @@ public void FireEventAndHandler(IApplicationEvent applicationEvent) } } + const int MutexWaitTimeoutInMilliseconds = 50; // ReSharper disable once MemberCanBePrivate.Global public void HandleActivity(IApplicationActivity activity) { + Mutex? mutex = null; + if (activity is IMutexed mutexSource) + { + mutex = mutexSource.GetMutex(ServiceProvider); + } + + var mutexAcquired = mutex?.WaitOne(MutexWaitTimeoutInMilliseconds) ?? true; + if (!mutexAcquired) + { + // place the activity back in the queue and free up the worker to make progress on a different activity + Dispatch(activity); + return; + } + try { _logger.LogDebug( @@ -149,6 +164,13 @@ public void HandleActivity(IApplicationActivity activity) { Fire(new UnhandledExceptionEvent(error)); } + finally + { + if (mutex != null && mutexAcquired) + { + mutex.ReleaseMutex(); + } + } } // ReSharper disable once MemberCanBePrivate.Global diff --git a/Corgibytes.Freshli.Cli/Functionality/Engine/IMutexed.cs b/Corgibytes.Freshli.Cli/Functionality/Engine/IMutexed.cs new file mode 100644 index 000000000..94a9697cc --- /dev/null +++ b/Corgibytes.Freshli.Cli/Functionality/Engine/IMutexed.cs @@ -0,0 +1,9 @@ +using System; +using System.Threading; + +namespace Corgibytes.Freshli.Cli.Functionality.Engine; + +public interface IMutexed +{ + public Mutex GetMutex(IServiceProvider serviceProvider); +}