From 48b80acd29360b926ab83443977ff821cdb92ccf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Sep 2026 11:59:03 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B3=D0=BE=D0=BD=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BB=D0=BB=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D1=87=D0=B8=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20(#1734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HashSet в EnumerationContext.WarnDeprecation не потокобезопасен: при одновременном первом обращении из фоновых заданий возникал IndexOutOfRangeException. Co-authored-by: Andrei Ovsiankin --- .../Machine/Contexts/EnumerationContext.cs | 7 +-- .../ObsoleteMembersTest.cs | 60 ++++++++++++++++++- tests/tasks.os | 22 +++++++ 3 files changed, 84 insertions(+), 5 deletions(-) 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..661fd8b03 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, _ => + { + 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, _ => + { + 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 По КоличествоЗаданий Цикл + Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПрочитатьЗначениеПеречисления", , Истина)); + КонецЦикла; + + ФоновыеЗадания.ОжидатьВсе(Задания); + ПроверитьОтсутствиеОшибокВЗаданиях(Задания); + +КонецПроцедуры + Процедура Пустышка() Экспорт КонецПроцедуры From 9df5d111ef94fb0d64b5b251959a6c0902565556 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Sep 2026 12:01:47 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=B8=D0=BB?= =?UTF-8?q?=D1=8F=D1=86=D0=B8=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BB=D0=BB?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BF=D0=B5=D1=80=D0=B5=D1=87=D0=B8=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrei Ovsiankin --- src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs b/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs index 661fd8b03..d5e8d19cf 100644 --- a/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs +++ b/src/Tests/OneScript.Core.Tests/ObsoleteMembersTest.cs @@ -175,7 +175,7 @@ public void ConcurrentEnumValueAccessDoesNotThrow() var enumInstance = CreateDeprecatedEnumInstance(); var exceptions = new ConcurrentBag(); - Parallel.For(0, 1000, _ => + Parallel.For(0, 1000, i => { try { @@ -197,7 +197,7 @@ public void ConcurrentDeprecatedEnumValueAccessWarnsOnce() var enumInstance = CreateDeprecatedEnumInstance(); var exceptions = new ConcurrentBag(); - Parallel.For(0, 1000, _ => + Parallel.For(0, 1000, i => { try {