Status: Accepted
Date: 2026-04-18
Pipeline генерации протокола использует StructuredTaskScope (Java 25) для параллельной генерации выписок. Каждый subtask исполняется в отдельном virtual thread, который не наследует ThreadLocal из родителя.
Что нужно пропагировать в subtask:
SecurityContext — Feign-клиенты читают через SecurityContextHolder (ThreadLocal-based в Spring Security 7).ThreadContext / MDCContext (тоже ThreadLocal).Референс OBESRABUO_protocol решает это вручную: ScopedValue + ручной bridge SECURITY_CTX.get() → SecurityContextHolder.setContext(...) + MDC.setContextMap(...) перед каждым subtask. Этот код был написан до выхода Spring Security 6.5 с нативным Micrometer-интеграциями.
Используем Micrometer Context Propagation как стандартный Spring-way для context propagation через virtual threads.
// Один раз перед всеми fork'ами:
ContextSnapshot snapshot = ContextSnapshot.captureAll();
try (var scope = StructuredTaskScope.open(Joiner.<R>allSuccessfulOrThrow())) {
items.forEach(item -> scope.fork(() -> {
try (var restore = snapshot.setThreadLocals()) {
return task.apply(item); // SecurityContext, MDC, Observation — на месте
}
}));
scope.join();
// собрать результаты через Subtask::get
}
ContextSnapshot.captureAll() собирает все зарегистрированные ThreadLocalAccessor:
SecurityContextHolderThreadLocalAccessor (Spring Security 6.5+) — автоматически регистрируется через ServiceLoader, если io.micrometer:context-propagation в classpath.MdcThreadLocalAccessor один раз в integration/config/ (если не окажется готового в micrometer-observation-slf4j bridge).RequestAttributesThreadLocalAccessor / LocaleContextThreadLocalAccessor (Spring Framework предоставляет, надо явно зарегистрировать).В crcm-protocol-service/pom.xml (parent) или в integration/core pom:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>context-propagation</artifactId>
</dependency>
Версия управляется через Spring Boot parent BOM.
Везде где StructuredTaskScope.open(...) в integration-адаптерах:
AgendaFeignAdapter.loadSnapshot — 3 параллельных Feign-вызова.ExtractSynthesizerAdapter.ensureForQuestions — параллельная генерация SIMPLE-выписок (Phase 2).ExtractSynthesizerAdapter или ProtocolAssembler — параллельная обработка SHORT/PRKK strip (Phase 3).Positive:
ThreadLocalAccessor-ами — Observation (traceId), Session, LocaleContext.try (snapshot.setThreadLocals()) { ... } — одна строчка.SecurityContextHolder — если появится ScopedValue-based alternative, snapshot перехватит.Negative:
io.micrometer:context-propagation. Но она скорее всего уже транзитивна через micrometer-observation / Spring Boot autoconfiguration.ScopedValue bridge как в референсе: работает, но boilerplate, не расширяется на новые context'ы, не covered документацией Spring.DelegatingSecurityContextCallable (Spring Security из коробки): пробрасывает только SecurityContext. Для MDC / Observation всё равно нужен свой код. Для простых случаев "только Security" — OK, но у нас нужно всё.InheritableThreadLocal через MODE_INHERITABLETHREADLOCAL: не работает в StructuredTaskScope с виртуальными потоками — ThreadLocal не копируется (virtual threads принципиально не inherit).SecurityContextHolderThreadLocalAccessorStructuredTaskScope (complements Joiner.allSuccessfulOrThrow).reference/OBESRABUO_protocol/.../ProtocolService.java — withSecurityContext + ScopedValue ручной bridge.