diff --git a/src/ScriptEngine/Machine/Contexts/EnumerationContext.cs b/src/ScriptEngine/Machine/Contexts/EnumerationContext.cs index b5708f5b8..2b0398ba7 100644 --- a/src/ScriptEngine/Machine/Contexts/EnumerationContext.cs +++ b/src/ScriptEngine/Machine/Contexts/EnumerationContext.cs @@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using OneScript.Commons; using OneScript.Contexts; @@ -20,7 +21,7 @@ public abstract class EnumerationContext : PropertyNameIndexAccessor, ICollectio private readonly IndexedNameValueCollection _values; private readonly List _definitions; private readonly TypeDescriptor _valuesType; - private readonly HashSet _checkedDeprecatedProps = new HashSet(); + private readonly ConcurrentDictionary _checkedDeprecatedProps = new ConcurrentDictionary(); protected EnumerationContext(TypeDescriptor typeRepresentation, TypeDescriptor valuesType) : base(typeRepresentation) { @@ -85,15 +86,13 @@ private EnumerationValue GetPropValueInternal(int propNum) private void WarnDeprecation(int propNum) { - if (_checkedDeprecatedProps.Contains(propNum)) + if (!_checkedDeprecatedProps.TryAdd(propNum, 0)) return; if (GetPropertyInfo(propNum) is SystemPropertyInfo { IsDeprecated: true }) { SystemLogger.Write($"Обращение к устаревшему свойству {GetPropertyInfo(propNum).Name}."); } - - _checkedDeprecatedProps.Add(propNum); } public override string GetPropName(int propNum) diff --git a/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs b/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs index f0ef06648..d5e8d19cf 100644 --- a/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs +++ b/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs @@ -6,7 +6,9 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading.Tasks; using FluentAssertions; using Moq; using OneScript.Exceptions; @@ -166,5 +168,61 @@ public void DeprecatedEnumValueHasWarning() _messages.Should().HaveCount(1) .And.Contain(x => x.Contains("СтароеЗначение2", StringComparison.InvariantCultureIgnoreCase)); } + + [Fact] + public void ConcurrentEnumValueAccessDoesNotThrow() + { + var enumInstance = CreateDeprecatedEnumInstance(); + + var exceptions = new ConcurrentBag(); + Parallel.For(0, 1000, i => + { + try + { + _ = enumInstance["Значение1"]; + } + catch (Exception e) + { + exceptions.Add(e); + } + }); + + exceptions.Should().BeEmpty(); + _messages.Should().BeEmpty(); + } + + [Fact] + public void ConcurrentDeprecatedEnumValueAccessWarnsOnce() + { + var enumInstance = CreateDeprecatedEnumInstance(); + + var exceptions = new ConcurrentBag(); + Parallel.For(0, 1000, i => + { + try + { + _ = enumInstance["СтароеЗначение2"]; + } + catch (Exception e) + { + exceptions.Add(e); + } + }); + + exceptions.Should().BeEmpty(); + _messages.Should().HaveCount(1) + .And.Contain(x => x.Contains("СтароеЗначение2", StringComparison.InvariantCultureIgnoreCase)); + } + + private static ClrEnumWrapper CreateDeprecatedEnumInstance() + { + var env = new RuntimeEnvironment(); + var discoverer = new ContextDiscoverer(new DefaultTypeManager(), Mock.Of(), new TinyIocImplementation()); + discoverer.DiscoverGlobalContexts(env, typeof(ObsoleteMembersTest).Assembly, t => t == typeof(DeprecatedEnum)); + + var enumInstance = env.GetGlobalProperty("НовоеПеречисление") as ClrEnumWrapper; + enumInstance.Should().NotBeNull(); + return enumInstance!; + } } -} \ No newline at end of file +} diff --git a/tests/tasks.os b/tests/tasks.os index d39f48ed4..0f44c1da5 100644 --- a/tests/tasks.os +++ b/tests/tasks.os @@ -40,6 +40,7 @@ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоОжиданиеНеСтираетНовыеЗадания"); ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоВИнформацииОбОшибкеЕстьСтекВызовов"); ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоОбработчикиСобытийВызываютсяВФоновомЗадании"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьПараллельноеЧтениеЗначенияСистемногоПеречисления"); Возврат ВсеТесты; @@ -647,6 +648,27 @@ КонецПроцедуры +Процедура ПрочитатьЗначениеПеречисления() Экспорт + Значение = НаправлениеПоиска.СКонца; + Если Значение = Неопределено Тогда + ВызватьИсключение "Не удалось прочитать значение системного перечисления"; + КонецЕсли; +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьПараллельноеЧтениеЗначенияСистемногоПеречисления() Экспорт + + КоличествоЗаданий = 200; + Задания = Новый Массив; + + Для НомерЗадания = 1 По КоличествоЗаданий Цикл + Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПрочитатьЗначениеПеречисления", , Истина)); + КонецЦикла; + + ФоновыеЗадания.ОжидатьВсе(Задания); + ПроверитьОтсутствиеОшибокВЗаданиях(Задания); + +КонецПроцедуры + Процедура Пустышка() Экспорт КонецПроцедуры