-
Notifications
You must be signed in to change notification settings - Fork 119
Функция ТекущийПоток(): данные и событие завершения потока исполнения #1725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 2 commits
6c2da66
3b5d116
3cb707c
d633efe
e9cadbc
1e5c1d0
e6df864
eb5a996
4ee6035
12861a5
e58c3d1
7284463
e29b496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,8 +123,7 @@ private void ConfigureApp() | |
|
|
||
| _app.Use((context, next) => | ||
| { | ||
| var process = _executionContext.Services.Resolve<IBslProcessFactory>().NewProcess(); | ||
| context.Items.Add(typeof(IBslProcess), process); | ||
| GetOrCreateProcess(context); | ||
| return next(); | ||
| }); | ||
|
|
||
|
|
@@ -178,7 +177,11 @@ private void UseBslExceptionHandler() | |
| var methodNumber = _exceptionHandler?.Target.GetMethodNumber(_exceptionHandler?.MethodName) | ||
| ?? throw new InvalidOperationException(); | ||
|
|
||
| var process = _executionContext.Services.Resolve<IBslProcessFactory>().NewProcess(); | ||
| // Обработчик исключений работает в том же процессе, что и обработчик запроса, | ||
| // поэтому видит контекст исполнения, в котором возникла ошибка. | ||
| // Собственный процесс создаётся только если исключение возникло до того, | ||
| // как процесс запроса был создан (например, в middleware статических файлов). | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Верно, и вы точнее кролика: веб-сокеты действительно были единственным middleware между Сейчас это неактуально с обеих сторон. Процесс выдаёт scoped-сервис и создаётся лениво, а область сервисов запроса переиспользуется |
||
| var process = GetOrCreateProcess(context); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| try | ||
| { | ||
|
|
@@ -197,6 +200,22 @@ private void UseBslExceptionHandler() | |
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Возвращает bsl-процесс, обслуживающий текущий запрос, создавая его при первом обращении. | ||
| /// Один запрос всегда обслуживается одним процессом, поэтому весь bsl-код запроса | ||
| /// видит один и тот же ИдентификаторПотокаИсполнения. | ||
| /// </summary> | ||
| private IBslProcess GetOrCreateProcess(HttpContext context) | ||
| { | ||
| if (context.Items.TryGetValue(typeof(IBslProcess), out var stored) && stored is IBslProcess existing) | ||
| return existing; | ||
|
|
||
| var process = _executionContext.Services.Resolve<IBslProcessFactory>().NewProcess(); | ||
| context.Items[typeof(IBslProcess)] = process; | ||
|
|
||
| return process; | ||
| } | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Здесь кролик абсолютно прав. Лучше не класть это в словарь запроса, оставив его полностью прикладным. Надо сделать scoped сервис, который уже делает получение или создание процесса. Кролик предлагает Features, но я не знаю что это и где может стрельнуть, никогда не пользовался.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Сделал scoped-сервисом,
Два побочных эффекта, оба в плюс:
Проверял на 4 одновременных запросах: обработчик исключений в каждом читает из |
||
|
|
||
| private static void WriteExceptionToResponse(HttpContext httpContext, Exception ex) | ||
| { | ||
| httpContext.Response.StatusCode = 500; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Замечание резонное. Я вообще предполагал метод ТекущийПоток() у которого были бы свойства, в т.ч. например соответствие, которое будет работать как набор тредлокалов и которое принудительно диспоузится вместе со всеми элементами в конце процесса. Не настаиваю, просто такой подход позволил бы вместо id процесса использовать собственно инстанс процесса.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сделал по вашему предложению,
ИдентификаторПотокаИсполнения()убрал.Теперь
ТекущийПоток()возвращаетПотокИсполнениясо свойствамиИдентификаториДанные:Экземпляр привязан к bsl-процессу через
ConditionalWeakTable, поэтому в пределах одной единицы исполненияТекущийПоток()всегда отдаёт один и тот же объект, а запись исчезает вместе с процессом сама, даже если владелец забыл его завершить.Завершают поток явно владельцы процесса: менеджер фоновых заданий по завершении задания и веб-сервер по окончании обработки запроса (через
Disposeтого самого scoped-сервиса из соседнего треда). При завершении соответствие очищается, а значения, поддерживающиеIDisposable, освобождаются — как вы и описывали.Побочно это закрывает и исходное замечание про переполнение: ключом для хранения состояния служит сам объект потока, а не число, так что виток
Int32уже ничего не ломает. Идентификатор остался только для диагностики и журналирования, о чём написано в его документации.Тесты в
tests/tasks.os: уникальность потока, изоляцияДанныхмежду одновременными фоновыми заданиями и освобождение данных с принудительнымDisposeэлементов. Писал их до реализации — тест на освобождение сначала падал сСравниваемые значения (0; 1) не равны, то есть данные переживали задание.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Дополню: к
ТекущийПоток()добавилось событие завершения потока — оно закрывает то, чего одних только данных не хватало.Освобождение
Данныхдобирается лишь до значений сIDisposableсреды CLR. Прикладным библиотекам этого мало: соединение с БД вentity— обычный BSL-объект, и узнать о конце единицы исполнения ему было неоткуда, кроме опроса списка фоновых заданий, который не видит ни запросов веб-сервера, ни последствийФоновыеЗадания.Очистить().Подписка идёт штатным механизмом, движку для неё ничего доделывать не пришлось:
Событие поднимается до очистки данных, поэтому обработчик ещё видит их содержимое. Ошибка обработчика наружу не выпускается: у фонового задания завершение идёт в
finallyи затёрло бы исходную ошибку, у веб-сервера выполняется уже после отправки ответа.Попутно пришлось добавить
IEventProcessor.RemoveAllHandlers: реестр подписокDefaultEventProcessorдержит источник до конца работы движка, а поток исполнения живёт лишь до конца своей единицы исполнения — без снятия подписок каждый обработанный запрос оставлял бы в реестре запись навсегда. Метод объявлен с пустой реализацией по умолчанию, чтобы не ломать сторонние процессоры событий.Схему обкатал на двух своих библиотеках. В
opentelemetryстек контекстов переехал вТекущийПоток().Данные: ушли синхронизированная карта, ручная сборка мусора и опрос заданий, а шесть одновременных запросов перестали читать чужие значения и падать сIndex is out of rangeна общем массиве. Вentityпул соединений перешёл с опроса на подписку: исчезлиРаботает(),ПотокАктивени повторы наCollection was modified, а брошенное соединение возвращается сразу по завершении потока, а не при следующем исчерпании предела.