Global Coroutine Usage
ID |
kotlin.global_coroutine_usage |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Kotlin |
Tags |
concurrency, reliability |
Description
Reports calls to coroutine builders launched on the GlobalScope singleton —
GlobalScope.launch { … } and GlobalScope.async { … }.
A coroutine started on GlobalScope is not bound to any structured-concurrency
parent. Its lifetime is the lifetime of the whole application: cancelling the
caller has no effect on the coroutine, exceptions thrown inside are silently
ignored unless explicitly handled, and resource leaks are easy to introduce.
Rationale
// Bad — fire-and-forget that outlives the caller
GlobalScope.launch { fetch() }
GlobalScope.async { fetch() }
GlobalScope was originally provided for top-level long-running tasks, but in
modern Kotlin it is @DelicateCoroutinesApi: every call site bypasses
structured concurrency. The launched coroutine keeps running after the surrounding
component is destroyed; on Android in particular this means activities and
view models can leak through their captured this reference.
Remediation
Launch on a caller-supplied CoroutineScope. Use a lifecycle-tied scope where
one exists (viewModelScope, lifecycleScope), or take the scope as a
constructor / function parameter so the lifetime is controlled by the caller.
class Loader(private val scope: CoroutineScope) {
fun load() {
scope.launch { fetch() }
scope.async { fetch() }
}
}