实现或修改 IntelliJ `AnAction`,并在 `plugin.xml` 中注册。将显示名称和说明写入消息资源文件,按需设置图标,并通过事件协程作用域启动挂起任务,避免同步处理阻塞 IDE。
- 为 IDE 菜单添加操作
- 修改操作注册或图标
- 整理操作名称和说明
检查IntelliJ产品中的Kotlin协程代码,梳理作用域归属、取消与失败的传播方式,并确认长任务和Flow收集能否响应取消,针对泄漏或取消遗漏提出结构调整建议。
Write or review IntelliJ coroutine scope ownership and cancellation.
实测评分 未实测
查看安装方法此技能用于检查IntelliJ产品中的Kotlin协程代码,梳理每个作用域由哪个对象拥有,以及何时会被取消。它会检查取消和失败如何在父子协程间传播,并确认长时间运行的任务或Flow收集是否配合取消。发现问题时,会说明影响并提出较小的结构调整建议。检查对象包括GlobalScope、脱离父级的作用域、被吞掉的取消异常,以及试图用外层try捕获launch失败的代码。
添加协程,或排查任务泄漏、无法停止的循环、取消后的状态不一致时,可以使用此技能。它也适用于希望让任务生命周期与服务或界面生命周期关联的开发者。
Flow收集过程是否遗漏了取消处理。”适用范围是IntelliJ产品中的Kotlin协程代码。界面或EDT(事件分发线程)的归属关系,以及协程内部机制的详细说明,由相关技能处理。
本介绍由 AI(OpenAI Codex)根据公开的 SKILL.md 撰写。准确的规格请查看下方原文。
AI 根据 SKILL.md 的内容检查了需要注意的操作和使用前提。评分只依据实际运行的结果(实测对比)。
不计入评分。 由 AI(OpenAI Codex)于 2026年10月28日 评估。仅供参考,安装前请同时查看原文。
安装前可以查看交给 AI 代理的指令内容。
Keep cancellation, failures, and lifetimes predictable in Kotlin coroutine code across any IntelliJ-based product module. Three rules govern every decision:
coroutineScope, supervisorScope, a
lifecycle-bound scope) over hand-rolled scope wiring, the most common source of leaks.Reach for this skill when changing suspend functions, launch/async, CoroutineScope
construction, cancellation handling, or Flow collection — and when diagnosing leaked coroutines,
uncancellable loops, swallowed cancellation, GlobalScope, launch-in-init, detached scopes,
invokeOnCompletion, ProcessCanceledException, blocking/progress bridges, ModalityState
context capture, or promise/callback cancellation bridges.
Related skill: kotlin-ui-swing-component-architecture (Swing UI and EDT ownership).
Deeper reading on the platform internals:
docs/IntelliJ-Platform/4_man/Kotlin-Coroutines/ (services, EDT
dispatchers, read actions), docs/IntelliJ-Platform/4_man/Performance/ (freezes, background write actions,
profiling) and docs/IntelliJ-Platform/4_man/Indexes/ (the indexing system, dumb-aware code).CoroutineScope and the exact moment it is cancelled (Disposable disposal,
service teardown, explicit cancel()). If you cannot name that moment, that is the finding.Flow collectors) for a
cooperative cancellation checkpoint — do not flag short bodies that already suspend.catch on the path for a swallowed CancellationException / ProcessCanceledException.invokeOnCompletion, GlobalScope, manual CoroutineScope(...), or launch in init
against the rules below, then classify (severity table) and propose the smallest structural fix.Never use GlobalScope for feature work — it is never cancelled, so its coroutines outlive the
component that started them.
Prefer an injected or platform lifecycle scope (project/service scope, or the scope handed to
you). A standalone CoroutineScope(context) with no Job in the context gets a fresh root Job()
not linked to any parent — nothing cancels or awaits it, so it leaks:
// Wrong: detached root scope; cleanup depends on a manual cancel() that is easy to miss.
val scope = CoroutineScope(someContextElement.asContextElement())
// Right: child of an owning scope; cancelled structurally with its parent.
val scope = owner.coroutineScope.childScope("MyFeature", someContextElement.asContextElement())
If a manual scope is unavoidable, either make it a child of an owning scope, or register the
owning Disposable (cancel the scope in dispose()) before launching any work, and verify that
registration actually runs. Disposal-only cancellation with no parent link is fragile — a missed
dispose() leaks everything the scope launched.
launch in init {} — depends on what cancels the scope. If the object owns a scope tied to
its own Disposer registration, don't launch in init: the coroutine can run (touching a half-built
this) before registration completes and leak on failure — expose start() and call it after
Disposer.register. A @Service with an injected CoroutineScope is the exception: that scope
is cancelled with the service's container (app/project/plugin) by the platform — not via your
dispose(), and no Disposable is needed solely to cancel the injected scope — so a service
self-starting workers from init is acceptable. Just keep the constructor cheap and don't read
not-yet-initialized state.
// Owned scope, cancelled via your own Disposer registration:
init { scope.launch { observe() } } // Wrong — may run before Disposer.register completes
fun start() { scope.launch { observe() } } // Right — after Disposer.register(owner, this)
// @Service(private val scope: CoroutineScope) — self-start from init is fine; scope is platform-managed.
Prefer the shortest lifecycle that consumes the work; use an app/project service scope only when the result is genuinely owned for that whole lifetime.
Do not store per-submission UI context in a long-lived scope. Context elements such as current
ModalityState must be captured at the submit site, not at service/object construction. Avoid
CoroutineScope(ModalityState.defaultModalityState().asContextElement()); prefer
scope.launch(currentModality.asContextElement()) { ... } when modality belongs to that operation.
Modality dispatch semantics belong to kotlin-ui-swing-component-architecture /
platform-deep-dives.
CancellationException cancels its parent and siblings. SupervisorJob/supervisorScope opts
child failure out of cancelling the parent; parent cancellation still cancels supervised children.coroutineScope { } is all-or-nothing (one failure fails the scope and rethrows); supervisorScope { }
is only for genuinely independent children.cancel(). Add a
suspension point (delay, yield), ensureActive(), or checkCanceled() in progress-aware code.suspendCancellableCoroutine (not suspendCoroutine) and release
the resource in invokeOnCancellation.Dispatchers.IO) or, for progress-aware
blocking code, coroutineToIndicator { indicator -> ... }. Pair cancellation with an explicit
interrupt/close strategy; coroutine cancellation alone does not abort an in-progress blocking call.
Do not wrap blocking code in blockingContext: deprecated since 2024.2 because context is
installed implicitly (ReplaceWith("action()")).runCatching { launch { ... } } does not catch the child's failure. The child fails its parent
scope out-of-band while launch returns normally, so try/runCatching sees nothing. To isolate a
child, use supervisorScope and handle the failure inside the child.
The same trap applies to async: try/runCatching around async { } does not contain a
later failure — the exception surfaces at await(), and an unsupervised failure may already have
cancelled the parent before you await.
Never swallow cancellation. runCatching and bare catch (e: Throwable) catch
CancellationException too; swallowing it breaks structured concurrency and can hang cancellation.
ProcessCanceledException is a CancellationException subtype, so catching cancellation covers it
too. Rethrow cancellation first:
try {
body()
} catch (c: CancellationException) { // also covers ProcessCanceledException (a subtype)
throw c // never swallow cancellation
} catch (x: Throwable) {
handle(x)
}
When bridging to a non-coroutine promise/callback, settle it before rethrowing cancellation.
If a coroutine owns an AsyncPromise or a callback result, cancellation can skip the normal result
path and leave external awaiters pending. In catch (c: CancellationException), complete the
external primitive (setError(c) / cancel) before throw c. Plain Deferred does not need this —
cancellation completes it.
Do not report a caught CancellationException as a user-visible error, even if you rethrow it.
Do not call cancel() on the coroutine you are currently running in — it does not stop execution
immediately (only at the next suspension point) and poisons the surrounding scope. Return early or
throw CancellationException. A shared suspend fun must never cancel its caller's job.
Prefer to avoid it. Three failure modes make it a trap:
check-then-act on a shared field races; use
AtomicReference.compareAndSet.Preferred alternative — do completion work as the last step inside the coroutine:
scope.launch {
try { doWork() }
finally { withContext(NonCancellable + Dispatchers.EDT) { onFinished() } }
}
Use NonCancellable only for small, bounded cleanup that must run even after cancellation — never
wrap substantial work in it, and keep UI cleanup lifecycle/disposal-guarded (do not touch a disposed
component). Cancelling an already-completed Job is a no-op, so "clear my job handle on completion"
bookkeeping is often unnecessary — verify it changes behavior before adding it.
Adjust for actual impact.
| Pattern | Default | Smallest fix |
|---|---|---|
GlobalScope for feature work | Critical | child of a lifecycle scope |
Detached CoroutineScope never cancelled | Critical | childScope of an owning scope |
Swallowed CancellationException/ProcessCanceledException | Critical | rethrow cancellation before catch (Throwable) |
| Non-cooperative infinite/long loop | Critical | add ensureActive()/yield/suspension point |
runCatching/try around launch/async to contain failure | Critical | supervisorScope + handle inside child |
launch in init with a scope tied to own Disposer registration | Major | start() after registration |
Manual scope where childScope fits; scope not tied to lifecycle | Major | inject/childScope; register Disposable before launch |
invokeOnCompletion mutating shared state unsynchronized or that can throw | Major | compareAndSet, or clean up in coroutine finally |
Self-cancellation (cancel() on current job) | Major | return / throw CancellationException |
| Promise/callback bridge left pending on cancellation | Major | complete/cancel external primitive before rethrow |
Unnecessary invokeOnCompletion bookkeeping | Minor | remove it |
blockingContext wrapper (deprecated 2024.2; context now implicit) | Minor | delete the wrapper |
| Cancellation reported as a user-visible error | Minor | rethrow silently |
supervisorScope/SupervisorJob where a plain scope suffices | Minor | use coroutineScope |
For deep internals see platform-deep-dives (coroutine notebooks: cancellation model, context
propagation) and the ultimate coroutine docs docs/IntelliJ-Platform/4_man/Kotlin-Coroutines/,
especially 9_Gotchas-and-practices/ and 8_UI-EDT-Dispatchers.md.
还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
实现或修改 IntelliJ `AnAction`,并在 `plugin.xml` 中注册。将显示名称和说明写入消息资源文件,按需设置图标,并通过事件协程作用域启动挂起任务,避免同步处理阻塞 IDE。
将 IntelliJ JPS 测试模块迁移到 Bazel,并排查仅在 Bazel 中出现的失败。根据症状检查依赖、插件、测试数据和 runfiles,重新生成并验证目标。
按 IntelliJ 项目规范编写或审查 Kotlin 和 Java 代码,涵盖语言选择、格式、密封类型、线程注解及控制流异常处理。
本指南帮助您按 IntelliJ 项目规则拆分更改并撰写提交信息:有工单时使用编号和变更说明,无工单时使用标签与子系统名,并在需要时说明原因和决定。
使用多个 Python 类型检查器比较代码片段或文件的类型推断和报错判断,并汇总各工具的结论、耗时与诊断信息,可保存为 Markdown 报告。
整理 Compose Desktop 界面中的状态和副作用,让可组合函数负责绘制,由展示器或协调器处理应用决策。了解状态归属、Effect、可复用组件接口、延迟列表键、动画和性能,适用于单项界面修改。