From fc98e108491e0affac57d49b51cfd09914b1470a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Sat, 27 Sep 2025 22:42:39 +0200 Subject: [PATCH 1/5] Thread-safety fixes --- .../DefaultRandomNumberProviderFactory.cs | 15 +++++-- .../Spice/Context/Updates/EntityUpdates.cs | 44 ++++++------------- .../Context/Updates/SimulationUpdates.cs | 20 ++------- ...ableStochasticModelsSimulationDecorator.cs | 22 +++------- 4 files changed, 35 insertions(+), 66 deletions(-) diff --git a/src/SpiceSharpParser/Common/Mathematics/Probability/DefaultRandomNumberProviderFactory.cs b/src/SpiceSharpParser/Common/Mathematics/Probability/DefaultRandomNumberProviderFactory.cs index 8060599f..037837df 100644 --- a/src/SpiceSharpParser/Common/Mathematics/Probability/DefaultRandomNumberProviderFactory.cs +++ b/src/SpiceSharpParser/Common/Mathematics/Probability/DefaultRandomNumberProviderFactory.cs @@ -48,10 +48,17 @@ public IRandomNumberProvider GetRandom(int? randomSeed) _cacheLock.EnterWriteLock(); try { - var randomGenerator = new DefaultRandomNumberProvider(new Random(randomSeed.Value)); - _randomGenerators[randomSeed.Value] = randomGenerator; - - return randomGenerator; + // Double-check after entering write lock + if (!_randomGenerators.ContainsKey(randomSeed.Value)) + { + var randomGenerator = new DefaultRandomNumberProvider(new Random(randomSeed.Value)); + _randomGenerators[randomSeed.Value] = randomGenerator; + return randomGenerator; + } + else + { + return _randomGenerators[randomSeed.Value]; + } } finally { diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/EntityUpdates.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/EntityUpdates.cs index e3d996c5..ba3c8f09 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/EntityUpdates.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/EntityUpdates.cs @@ -103,19 +103,14 @@ public void Add(IEntity entity, string parameterName, string expression, bool be throw new ArgumentNullException(nameof(expression)); } - if (!SimulationSpecificUpdates.ContainsKey(simulation)) - { - SimulationSpecificUpdates[simulation] = new Dictionary(); - } - - if (!SimulationSpecificUpdates[simulation].ContainsKey(entity)) - { - SimulationSpecificUpdates[simulation][entity] = new EntityUpdate(); - } + var simulationUpdates = SimulationSpecificUpdates.GetOrAdd(simulation, _ => new Dictionary()); + var entityUpdate = simulationUpdates.ContainsKey(entity) + ? simulationUpdates[entity] + : (simulationUpdates[entity] = new EntityUpdate()); if (beforeTemperature) { - SimulationSpecificUpdates[simulation][entity].ParameterUpdatesBeforeTemperature.Add(new EntityParameterExpressionValueUpdate() + entityUpdate.ParameterUpdatesBeforeTemperature.Add(new EntityParameterExpressionValueUpdate() { Expression = new DynamicExpression(expression), ParameterName = parameterName, @@ -140,14 +135,11 @@ public void Add(IEntity entity, string parameterName, string expression, bool be throw new ArgumentNullException(nameof(expression)); } - if (!CommonUpdates.ContainsKey(entity)) - { - CommonUpdates[entity] = new EntityUpdate(); - } + var entityUpdate = CommonUpdates.GetOrAdd(entity, _ => new EntityUpdate()); if (beforeTemperature) { - CommonUpdates[entity].ParameterUpdatesBeforeTemperature.Add(new EntityParameterExpressionValueUpdate() + entityUpdate.ParameterUpdatesBeforeTemperature.Add(new EntityParameterExpressionValueUpdate() { Expression = new DynamicExpression(expression), ParameterName = parameterName, @@ -167,14 +159,11 @@ public void Add(IEntity entity, string parameterName, double value, bool beforeT throw new ArgumentNullException(nameof(parameterName)); } - if (!CommonUpdates.ContainsKey(entity)) - { - CommonUpdates[entity] = new EntityUpdate(); - } + var entityUpdate = CommonUpdates.GetOrAdd(entity, _ => new EntityUpdate()); if (beforeTemperature) { - CommonUpdates[entity].ParameterUpdatesBeforeTemperature.Add( + entityUpdate.ParameterUpdatesBeforeTemperature.Add( new EntityParameterDoubleValueUpdate() { ParameterName = parameterName, Value = value }); } } @@ -196,19 +185,14 @@ public void Add(IEntity entity, string parameterName, double value, bool beforeT throw new ArgumentNullException(nameof(parameterName)); } - if (!SimulationSpecificUpdates.ContainsKey(simulation)) - { - SimulationSpecificUpdates[simulation] = new Dictionary(); - } - - if (!SimulationSpecificUpdates[simulation].ContainsKey(entity)) - { - SimulationSpecificUpdates[simulation][entity] = new EntityUpdate(); - } + var simulationUpdates = SimulationSpecificUpdates.GetOrAdd(simulation, _ => new Dictionary()); + var entityUpdate = simulationUpdates.ContainsKey(entity) + ? simulationUpdates[entity] + : (simulationUpdates[entity] = new EntityUpdate()); if (beforeTemperature) { - SimulationSpecificUpdates[simulation][entity].ParameterUpdatesBeforeTemperature.Add(new EntityParameterDoubleValueUpdate { ParameterName = parameterName, Value = value }); + entityUpdate.ParameterUpdatesBeforeTemperature.Add(new EntityParameterDoubleValueUpdate { ParameterName = parameterName, Value = value }); } } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/SimulationUpdates.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/SimulationUpdates.cs index f6150731..731efe68 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/SimulationUpdates.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Updates/SimulationUpdates.cs @@ -83,14 +83,8 @@ public void AddBeforeSetup(ISimulationWithEvents simulation, Action() { new SimulationUpdateAction(action) }; - } - else - { - SimulationBeforeSetupActions[simulation].Add(new SimulationUpdateAction(action)); - } + var actions = SimulationBeforeSetupActions.GetOrAdd(simulation, _ => new List()); + actions.Add(new SimulationUpdateAction(action)); } public void AddBeforeTemperature(ISimulationWithEvents simulation, Action action) @@ -100,14 +94,8 @@ public void AddBeforeTemperature(ISimulationWithEvents simulation, Action() { new SimulationUpdateAction(action) }; - } - else - { - SimulationBeforeTemperatureActions[simulation].Add(new SimulationUpdateAction(action)); - } + var actions = SimulationBeforeTemperatureActions.GetOrAdd(simulation, _ => new List()); + actions.Add(new SimulationUpdateAction(action)); } public void AddBeforeSetup(Action action) diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Simulations/Decorators/EnableStochasticModelsSimulationDecorator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Simulations/Decorators/EnableStochasticModelsSimulationDecorator.cs index 46a45e6a..1330c9dd 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Simulations/Decorators/EnableStochasticModelsSimulationDecorator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Simulations/Decorators/EnableStochasticModelsSimulationDecorator.cs @@ -1,5 +1,4 @@ using SpiceSharp.Entities; -using SpiceSharp.Simulations; using SpiceSharpParser.Common; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models; @@ -14,7 +13,7 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.Controls.Simulatio public class EnableStochasticModelsSimulationDecorator { private readonly IReadingContext _context; - private readonly ConcurrentDictionary> _lotValues = new (); + private readonly ConcurrentDictionary> _lotValues = new (); public EnableStochasticModelsSimulationDecorator(IReadingContext context) { @@ -98,22 +97,13 @@ private void SetModelDevModelParameters(ISimulationWithEvents simulation, IEntit private double GetValueForLotParameter(EvaluationContext evaluationContext, string baseModel, string parameterName, double currentValue, double percentValue, string distributionName, IEqualityComparer comparer) { - if (_lotValues.ContainsKey(baseModel) && _lotValues[baseModel].ContainsKey(parameterName)) - { - return _lotValues[baseModel][parameterName]; - } - - var random = evaluationContext.Randomizer.GetRandomProvider(distributionName); - double newValue = currentValue + ((percentValue / 100.0) * currentValue * random.NextSignedDouble()); + var modelValues = _lotValues.GetOrAdd(baseModel, _ => new ConcurrentDictionary(comparer)); - if (!_lotValues.ContainsKey(baseModel)) + return modelValues.GetOrAdd(parameterName, _ => { - _lotValues[baseModel] = new Dictionary(comparer); - } - - _lotValues[baseModel][parameterName] = newValue; - - return newValue; + var random = evaluationContext.Randomizer.GetRandomProvider(distributionName); + return currentValue + ((percentValue / 100.0) * currentValue * random.NextSignedDouble()); + }); } } } \ No newline at end of file From 5b56d8e5adf9461c95cdec391b8fc36e2f5e2853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Sat, 27 Sep 2025 22:52:36 +0200 Subject: [PATCH 2/5] Try to fix sonar --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 07683bd8..0fa8baab 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -55,7 +55,7 @@ jobs: args: # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) # mandatory - -Dsonar.projectKey="SpiceSharp_SpiceSharpParser" + -Dsonar.projectKey="SpiceSharp_SpiceSharpParserNew" -Dsonar.organization="spicesharp" # Comma-separated paths to directories containing main source files. #-Dsonar.sources= # optional, default is project base directory From 6e7fe33db9ceab794507fba2c5957185d276ccde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Sat, 27 Sep 2025 23:05:53 +0200 Subject: [PATCH 3/5] Revert --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 0fa8baab..07683bd8 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -55,7 +55,7 @@ jobs: args: # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) # mandatory - -Dsonar.projectKey="SpiceSharp_SpiceSharpParserNew" + -Dsonar.projectKey="SpiceSharp_SpiceSharpParser" -Dsonar.organization="spicesharp" # Comma-separated paths to directories containing main source files. #-Dsonar.sources= # optional, default is project base directory From 5234ac7437a440829d129b000a8e30c637f12fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Sat, 27 Sep 2025 23:06:23 +0200 Subject: [PATCH 4/5] Fix --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 07683bd8..0fa8baab 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -55,7 +55,7 @@ jobs: args: # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) # mandatory - -Dsonar.projectKey="SpiceSharp_SpiceSharpParser" + -Dsonar.projectKey="SpiceSharp_SpiceSharpParserNew" -Dsonar.organization="spicesharp" # Comma-separated paths to directories containing main source files. #-Dsonar.sources= # optional, default is project base directory From adee3e5b138a3c4fda33d85118d7a85358517507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Sat, 27 Sep 2025 23:09:00 +0200 Subject: [PATCH 5/5] Increase version --- src/SpiceSharpParser/SpiceSharpParser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SpiceSharpParser/SpiceSharpParser.csproj b/src/SpiceSharpParser/SpiceSharpParser.csproj index 45136843..c1b1a7d7 100644 --- a/src/SpiceSharpParser/SpiceSharpParser.csproj +++ b/src/SpiceSharpParser/SpiceSharpParser.csproj @@ -22,7 +22,7 @@ MIT latest - 3.2.5 + 3.2.6