From 9f9167cb9cb7b03e4efe43fbf0406982ea376944 Mon Sep 17 00:00:00 2001 From: sfaqer Date: Wed, 30 Sep 2026 20:32:53 +1000 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D0=B0=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D0=B1=D0=B8=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Параллельный #Использовать одной незагруженной библиотеки из фоновых заданий давал ложную циклическую зависимость, повторную загрузку или неинициализированный модуль. Теперь библиотеки грузятся по одной, а вставка глобальных свойств и контекстов в окружение идет под блокировкой. Co-Authored-By: Claude Opus 5.5 --- .../FileSystemDependencyResolver.cs | 14 +++- src/ScriptEngine/RuntimeEnvironment.cs | 71 ++++++++++++------- tests/librarytest.os | 65 ++++++++++++++++- tests/slowlib/module.os | 3 + tests/slowlib/package-loader.os | 8 +++ 5 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 tests/slowlib/module.os create mode 100644 tests/slowlib/package-loader.os diff --git a/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs b/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs index f5fd6b45b..affee791b 100644 --- a/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs +++ b/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs @@ -243,6 +243,17 @@ private LoadResult LoadByPath(string libraryPath, IBslProcess process) } private PackageInfo LoadLibraryInternal(string libraryPath, IBslProcess process) + { + // Библиотеки грузятся по одной: иначе поток, которому нужна библиотека, загружаемая + // сейчас в другом потоке, принимает ее за циклическую зависимость или грузит повторно. + // Вложенные #Использовать грузятся в том же потоке, блокировка их пропускает + lock (_libs) + { + return DoLoadLibrary(libraryPath, process); + } + } + + private PackageInfo DoLoadLibrary(string libraryPath, IBslProcess process) { var id = GetLibraryId(libraryPath); var existedLib = _libs.FirstOrDefault(x => x.id == id); @@ -261,7 +272,6 @@ private PackageInfo LoadLibraryInternal(string libraryPath, IBslProcess process) } var newLib = new Library() { id = id, state = ProcessingState.Discovered }; - int newLibIndex = _libs.Count; var customLoaderFile = Path.Combine(libraryPath, PREDEFINED_LOADER_FILE); if (File.Exists(customLoaderFile)) @@ -277,7 +287,7 @@ private PackageInfo LoadLibraryInternal(string libraryPath, IBslProcess process) } catch (Exception) { - _libs.RemoveAt(newLibIndex); + _libs.Remove(newLib); throw; } diff --git a/src/ScriptEngine/RuntimeEnvironment.cs b/src/ScriptEngine/RuntimeEnvironment.cs index bb450db38..8fc5cb008 100644 --- a/src/ScriptEngine/RuntimeEnvironment.cs +++ b/src/ScriptEngine/RuntimeEnvironment.cs @@ -42,8 +42,11 @@ private void CreateGlobalScopeIfNeeded() lock (_injectedProperties) { - _scopeOfGlobalProperties ??= _symbols.PushContext(_injectedProperties); - _contexts.Add(_injectedProperties); + if (_scopeOfGlobalProperties == null) + { + _scopeOfGlobalProperties = _symbols.PushContext(_injectedProperties); + _contexts.Add(_injectedProperties); + } } } @@ -85,48 +88,62 @@ private void InjectPropertyInternal( throw new ArgumentException("Invalid identifier", nameof(alias)); } CreateGlobalScopeIfNeeded(); - var num = _injectedProperties.Insert(value, identifier, true, !readOnly); - var bslPropertyInfo = _injectedProperties.GetPropertyInfo(num); - IVariableSymbol registeredSymbol; - if (ownerPackage == null) + // Номер свойства в значениях и номер символа в области видимости должны совпасть: + // вставки из разных потоков (библиотека, внешняя компонента) идут по одной + lock (_injectedProperties) { - registeredSymbol = new WrappedPropertySymbol(bslPropertyInfo) + var num = _injectedProperties.Insert(value, identifier, true, !readOnly); + + var bslPropertyInfo = _injectedProperties.GetPropertyInfo(num); + IVariableSymbol registeredSymbol; + if (ownerPackage == null) { - Name = identifier, - Alias = alias - }; - } - else - { - registeredSymbol = new WrappedLibraryPropertySymbol(bslPropertyInfo, ownerPackage) + registeredSymbol = new WrappedPropertySymbol(bslPropertyInfo) + { + Name = identifier, + Alias = alias + }; + } + else { - Name = identifier, - Alias = alias - }; + registeredSymbol = new WrappedLibraryPropertySymbol(bslPropertyInfo, ownerPackage) + { + Name = identifier, + Alias = alias + }; + } + + _scopeOfGlobalProperties.DefineVariable(registeredSymbol); } - - _scopeOfGlobalProperties.DefineVariable(registeredSymbol); } public void InjectGlobalProperty(IValue value, BslPropertyInfo definition) { CreateGlobalScopeIfNeeded(); - _injectedProperties.Insert(value, definition); - var symbol = new WrappedPropertySymbol(definition) + lock (_injectedProperties) { - Name = definition.Name, - Alias = definition.Alias - }; + _injectedProperties.Insert(value, definition); + + var symbol = new WrappedPropertySymbol(definition) + { + Name = definition.Name, + Alias = definition.Alias + }; - _scopeOfGlobalProperties.DefineVariable(symbol); + _scopeOfGlobalProperties.DefineVariable(symbol); + } } private void RegisterObject(IAttachableContext context) { - _symbols.PushContext(context); - _contexts.Add(context); + // Номер области видимости и номер контекста должны совпасть + lock (_injectedProperties) + { + _symbols.PushContext(context); + _contexts.Add(context); + } } public void SetGlobalProperty(string propertyName, IValue value) diff --git a/tests/librarytest.os b/tests/librarytest.os index 2bc0f56cc..62bd179f3 100644 --- a/tests/librarytest.os +++ b/tests/librarytest.os @@ -2,6 +2,7 @@ #Использовать "customlib" Перем юТест; +Перем ГотовыеЗадания; Функция ПолучитьСписокТестов(Знач Тесты) Экспорт юТест = Тесты; @@ -9,7 +10,8 @@ Список = Новый Массив; Список.Добавить("ТестДолжен_ИспользоватьВстроенныйЗагрузчик"); Список.Добавить("ТестДолжен_ИспользоватьПользовательскийЗагрузчик"); - + Список.Добавить("ТестДолжен_ЗагрузитьБиблиотекуИзНесколькихЗаданийОдновременно"); + Возврат Список; КонецФункции @@ -33,5 +35,64 @@ НекийОбщийМодуль.УстановитьПоСсылке(Супер.А); юТест.ПроверитьРавенство(100, Супер.А); - + +КонецПроцедуры + +Процедура ТестДолжен_ЗагрузитьБиблиотекуИзНесколькихЗаданийОдновременно() Экспорт + + Путь = ОбъединитьПути(ТекущийСценарий().Каталог, "slowlib"); + ГотовыеЗадания = Новый Массив(4); + Для Индекс = 0 По ГотовыеЗадания.ВГраница() Цикл + ГотовыеЗадания[Индекс] = Ложь; + КонецЦикла; + + Задания = Новый Массив; + Для Индекс = 0 По ГотовыеЗадания.ВГраница() Цикл + Параметры = Новый Массив; + Параметры.Добавить(Путь); + Параметры.Добавить(Индекс); + Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ИспользоватьМедленнуюБиблиотеку", Параметры)); + КонецЦикла; + ФоновыеЗадания.ОжидатьВсе(Задания); + + Для Каждого Задание Из Задания Цикл + Если Задание.ИнформацияОбОшибке <> Неопределено Тогда + ВызватьИсключение Задание.ИнформацияОбОшибке.ПодробноеОписаниеОшибки(); + КонецЕсли; + юТест.ПроверитьРавенство("Привет", Задание.Результат); + КонецЦикла; + КонецПроцедуры + +Функция ИспользоватьМедленнуюБиблиотеку(Путь, Индекс) Экспорт + + // Все задания начинают подключать библиотеку одновременно + ГотовыеЗадания[Индекс] = Истина; + Для Номер = 1 По 500 Цикл + Если ВсеЗаданияГотовы() Тогда + Прервать; + КонецЕсли; + Приостановить(10); + КонецЦикла; + + Сценарий = ЗагрузитьСценарийИзСтроки( + "#Использовать """ + Путь + """ + |Функция Привет() Экспорт + | Возврат МедленныйМодуль.Привет(); + |КонецФункции"); + Возврат Сценарий.Привет(); + +КонецФункции + +Функция ВсеЗаданияГотовы() + + // По индексу, а не Для Каждого: другие задания в это время пишут в массив + Для Индекс = 0 По ГотовыеЗадания.ВГраница() Цикл + Если Не ГотовыеЗадания[Индекс] Тогда + Возврат Ложь; + КонецЕсли; + КонецЦикла; + + Возврат Истина; + +КонецФункции diff --git a/tests/slowlib/module.os b/tests/slowlib/module.os new file mode 100644 index 000000000..4dc4885ab --- /dev/null +++ b/tests/slowlib/module.os @@ -0,0 +1,3 @@ +Функция Привет() Экспорт + Возврат "Привет"; +КонецФункции diff --git a/tests/slowlib/package-loader.os b/tests/slowlib/package-loader.os new file mode 100644 index 000000000..02c3fcdc1 --- /dev/null +++ b/tests/slowlib/package-loader.os @@ -0,0 +1,8 @@ +Процедура ПриЗагрузкеБиблиотеки(Знач Путь, СтандартнаяОбработка, Отказ) + + // Загрузка долгая: пока она идет, библиотеку запрашивают из других потоков + Приостановить(300); + СтандартнаяОбработка = Ложь; + ДобавитьМодуль(ОбъединитьПути(Путь, "module.os"), "МедленныйМодуль"); + +КонецПроцедуры