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() }
    }
}