Threading Highlighter — инструмент динамического анализа для разработчиков плагинов IntelliJ Platform. Он показывает, какие threading-контракты фактически действуют на конкретных строках кода.
Инструмент состоит из двух компонентов:
- Агент (сбор данных). Java-агент инструментирует threading-ассерты платформы. При каждом срабатывании агент обходит стек (
StackWalker) и записывает в JSONL-файлы фреймы кода анализируемого плагина — только из базовых пакетов, объявленных через белый список (threading.highlighter.include.packages). - Плагин (визуализация). Загружает записанные трассы и отображает gutter-иконки напротив строк, попавших в стек до ассерта.
Threading-контракты (EDT-only, non-EDT, slow operations, read/write access) не выражены в сигнатурах методов: обязательность контракта определяется тем, через какие ассерты платформы вызов фактически проходит в рантайме. Реальная цепочка вызовов складывается динамически — через колбэки платформы, invokeLater, пулы потоков и точки расширения plugin.xml.
Threading Highlighter наблюдает фактические срабатывания ассертов в работающей IDE и фиксирует, какой участок кода плагина к ним привёл. По gutter-иконке видно, через какой ассерт реально прошёл вызов.
Ограничение метода: инструмент показывает контракты только для путей выполнения, реально пройденных за сессию. Невоспроизведённый сценарий трасс не даст.
Инструментируются ассерты обеих осей threading-модели. Внутренний код платформы вызывает часть ассертов напрямую через ThreadingAssertions, минуя ApplicationImpl, — поэтому инструментируются оба класса.
| Контракт | Методы платформы |
|---|---|
| EDT (должен работать на UI-потоке) | ApplicationImpl#assertIsDispatchThread, ThreadingAssertions#assertEventDispatchThread |
| Non-EDT (фоновый поток) | ApplicationImpl#assertIsNonDispatchThread, ThreadingAssertions#assertBackgroundThread |
Read Access (runReadAction) |
ApplicationImpl#assertReadAccessAllowed, ThreadingAssertions#assertReadAccess |
Write Access (runWriteAction) |
ApplicationImpl#assertWriteAccessAllowed, ThreadingAssertions#assertWriteAccess |
| Slow Operation (тяжёлая операция/I-O) | SlowOperations#assertSlowOperationsAreAllowed |
Список фиксированный: агент копирует advice в эти методы на этапе загрузки классов. Расширение — одной записью в Markers.java.
ThreadingHighlighter/
├── common/ — разделяемые модели данных (чистая Java)
├── agent/ — Java-агент (Byte Buddy)
├── plugin/ — плагин визуализации
└── examples/ — демо-плагин с тестовыми actions
JVM #1 — рабочая IDE JVM #2 — sandbox-IDE (runIde)
├─ установлен плагин ├─ выполняется анализируемый код
├─ рисует gutter-иконки ├─ подключён агент (-javaagent)
└─ читает готовые .jsonl └─ агент пишет .jsonl
│
└─ обмен через файлы .ij-threading-highlighter/
-javaagent — флаг старта JVM, поэтому агент прописывается в build.gradle.kts анализируемого проекта.
- Целевой код — плагин IntelliJ Platform, запускаемый через
runIde(обычное JVM-приложение threading-ассерты не вызывает). - Задан белый список пакетов
threading.highlighter.include.packages— без него агент не захватит ни одного фрейма (граница пакета учитывается:com.exampleвключаетcom.example.Foo, но неcom.exampleOther.Bar). - Код плагина находится в стеке в момент срабатывания ассерта: ассерты, вызванные платформой для собственных нужд, в трассу не попадают.
- Рабочая IDE попадает в диапазон
sinceBuild/untilBuildплагина (текущий: build 253).
- Установите плагин в рабочую IDE:
Settings → Plugins → ⚙️ → Install Plugin from Disk…→plugin/build/distributions/plugin-0.1.0.zip. - Получите VM-аргументы:
Tools → Threading Highlighter → Enable Agent...— диалог выдаёт готовую строку-javaagent:… -Dthreading.highlighter.project.dir=… -Dthreading.highlighter.include.packages=<your.base.package>. - Вставьте строку в
runIdeанализируемого проекта, заменив<your.base.package>на базовый пакет плагина:
tasks {
runIde {
jvmArgs("-javaagent:/path/to/agent.jar")
systemProperty("threading.highlighter.project.dir", "${project.projectDir}")
systemProperty("threading.highlighter.include.packages", "com.example.myplugin")
}
}- Запустите
runIde, воспроизведите сценарии, закройте sandbox-IDE и вернитесь в рабочую IDE.
Сборка из исходников:
./gradlew :agent:shadowJar # agent/build/libs/agent-0.1.0.jar
./gradlew :plugin:buildPlugin # plugin/build/distributions/plugin-0.1.0.zipОбязательны: -javaagent, threading.highlighter.project.dir, threading.highlighter.include.packages.
Необязательные:
| Свойство | По умолчанию | Назначение |
|---|---|---|
threading.highlighter.append.session |
false |
Накопление трасс за несколько запусков (корректно только при неизменном коде) |
threading.highlighter.max.stack.depth |
128 |
Максимальная глубина захвата стека |
threading.highlighter.flush.interval.minutes |
15 |
Интервал сброса трасс на диск (полный сброс — при завершении JVM) |
threading.highlighter.min.capture.interval.millis |
0 |
Минимальный интервал между захватами для одного маркера |
- Трассы перезагружаются автоматически при изменении файлов; вручную —
Tools → Threading Highlighter → Reload Threading Traces. - Gutter-иконки появляются на строках, попавших в трассы. Если файл редактировался после записи, подсказка предупреждает о возможном смещении номера строки.
Show Trace Summary— сводка загруженных трасс;Hide/Show Threading Markers— включение/выключение значков.
Модуль examples — демонстрационный плагин с тестовыми actions, где подключение агента уже настроено. Он показывает инструмент в действии и не предназначен для анализа реальных проектов: используйте его, чтобы проверить установку и увидеть gutter-иконки, а затем подключайте агент к своему плагину по инструкции выше.
