Перейти к содержанию

ADR 0094: Шина доставки событий в UI (аналогия AFDX) и System.Threading.Channel<T>

Статус: Accepted
Дата: 2026-04-24

Связанные ADR

ADR Роль
0036 продуктовый канал кабины → CDS → композитор → поверхность
0097 CCU / вычислительный блок кабины: свёртка в DTO/снимок канала — не эта шина
0089 IDE Health вместо прежнего Workspace Health — термин и типы вроде IdeHealth*; не этот чат, а зафиксированное в репо решение
0021 зоны внимания; EICAS как продуктовый контур — по дорожной карте
0068 полезная нагрузка строки канала vs проекция
0007 сигналы и нагрузка на UI
0004 маршалинг на UI-поток

Резюме

  • Шина доставки в UI (аналогия AFDX): Channel<T>, батчинг, backpressure.
  • Ортогонально CDS-«каналу» 0036.

Вне ADR

Документ Роль
cds-contract-v0.md cds contract v0
---


Контекст

Уточнение по размещению UI (чтобы не путать с VS-like «нижней панелью» главного окна): в текущей разметке MainWindow нет отдельной строки grid на всю ширину под «терминал / сборка». Вторичный контур — колонка MFD: MfdShellView — сверху полоса WorkspaceChromeBandView (IDE Health по 0089; задел под EICAS-семантику — см. комментарий в MfdShellView.axaml и 0021), ниже хост MfdContourStackHost со MfdShellPageStack (страницы WORKSPACE / чат / журнал сборки / терминал / …). Этот ADR про транспорт потоков текста в соответствующие VM (например BuildOutputPanelViewModel), а не про то, что журнал MSBuild уже является полосой EICAS.

Несколько источников (MSBuild / dotnet, дочерние процессы, LSP, агент, внутренние сервисы) пишут текст и статусы в эти поверхности вторичного контура и смежные VM. Сегодня типичный путь — много обрывковых вызовов на UI-поток или частые обновления одного большого буфера (Build output, терминал и т.п.), из-за чего поток воспринимается как рваный, а UI — перегруженным мелкими инкрементами.

В авионике AFDX (ARINC 664 Part 7, switched Ethernet) — образ общей детерминированной шины: несколько LRU подключаются к ограниченному транспорту с политикой доступа и приоритетов, а не «каждый сам тянет провод в прибор».

Cascade не внедряет AFDX как протокол и не смешивает этот ADR с сертификацией; переносим только инженерный смысл: нормализованная доставка + backpressure + один предсказуемый путь к потребителю.


Решение

Вводится опциональный слой ingestion (рабочее имя в коде/доках — по согласованию, например IdeIngestion, BuildOutputIngestion; этот ADR не фиксирует имя пакета) для транспорта событий от продюсеров к VM страниц MFD и другим потребителям длинного текста:

  1. Продуктовый «канал» кабины в смысле 0036 и шина доставки в смысле этого ADR — разные вещи. Первый отвечает на вопрос что в кабине и куда по смыслу; второй — как донести куски текста/событий до привязки к свойствам UI без гонок и лавины обновлений.

  1. Рекомендуемая реализация в .NET: System.Threading.Channel<T> (часто BoundedChannelOptions + явный FullMode: ожидание, отброс хвоста или политика по продукту) для очереди типизированных записей (BuildLogLine, TerminalChunk, унифицированный IdeStreamEvent — выбор при внедрении).

  1. Один (или мало) читатель(ей) снимает канал и батчит применение к VM: например, не чаще N мс и/или не реже M символов за тик, затем один IUiScheduler/Post на обновление привязанного свойства (журнал сборки, терминал, …) (0004).

  1. Strangler: первый вертикальный срез — журнал сборки (и при необходимости терминал); расширение на другие потоки — после стабилизации паттерна. Существующие вызовы не обязаны мигрировать в один коммит.

  1. Наблюдаемость: снимок очереди / счётчики дропа (если политика допускает) — по мере необходимости для отладки и MCP; форма JSON не фиксируется этим ADR (см. 0008 при появлении полей).

Границы с CDS и с «каналом» в глоссарии

  • CDS и Cockpit/Channels/** остаются каноном семантики кабины и данных для приборов; этот ADR не добавляет в CDS ответственность за «все логи MSBuild».
  • Шина доставки живёт ниже или рядом с VM, которые кормят страницы MFD и аналогичные длинные логи (журнал сборки, терминал), не подменяя 0068: когда тот же смысл уже оформлен как полезная нагрузка строки канала кабины (в т.ч. будущие/текущие сегменты полосы в духе EICAS / IDE Health в CDS), он попадает в контур 0036 по существующим правилам — отдельно от сырого потока MSBuild в TextBox страницы «Build».
  • Свёртка сырья в снимок/DTO канала (агрегация, приоритеты, теги уровня A/B/C) — это не ответственность шины; см. 0097.

Последствия

  • Меньше «рваного» UI и проще тестировать продюсеров (писать в канал из теста, проверять батчи).
  • Явный backpressure вместо неограниченного роста строки в памяти при шторме лога.
  • Дополнительный слой абстракции: внедрять поэтапно, с измерением до/после (0054 при появлении сценариев).

Не цели

  • Реализация AFDX / ARINC 664 как сетевого стека.
  • Замена CDS или переименование продуктового термина «канал» в кабине.
  • Обязательная унификация всех сигналов IDE в одном Channel<object> без типизации.

Отклонённые альтернативы (кратко)

  • Только Rx / только ручная очередь без политики: допустимо локально; как дефолт продукта предпочтительнее канал с явными границами и простым single-reader loop.
  • Пихать всё в CDS: отклонено — смешивает кабину и транспорт логов shell (0036, 0066).

Состояние реализации

Сборка (UI и MCP), dotnet format (MCP) → панель «Сборка · вывод»: единая цепочка: IDotnetCommandRunner.RunWithChunkWriterAsyncBuildLogIngestion.CreateBuildLogChannel (bounded, DefaultChannelChunkCapacity ≈ 32, FullMode = Wait — backpressure к помпам stdout/stderr, без дропа) → BuildLogIngestion.DrainToAppendAsync (батч ~8K в Append, по желанию onEachDequeuedChunk в OutputAccumulator для полного сыря в ответе MCP) → BuildOutputPanelViewModel.Append (фаза 5: Post на UI).

MCP (McpDotnetBuildTestService.BuildWithBinlogAsync, RunCodeCleanupAsync): тот же транспорт; возвращаемая строка для JSON/парсеров = накопленное в OutputAccumulator (и суффикс Exit code при ошибке build).

Тесты: CascadeIDE.Tests/BuildLogIngestionTests (батч, onEach, фабрика bounded-канала).

Вне этого ADR (намеренно): dotnet test / вывод Инструментация · тесты — по-прежнему RunAsync и одна вставка в InstrumentationPanel (другая поверхность; при необходимости — тот же паттерн отдельно). Встроенный терминал (если/когда) — тот же strangler, не блокирует степень Accepted этой ADR.