Kotlin coroutines: Report usages of `GlobalScope`

kotlin

(Jonathan Cornaz) #1

Description

GlobalScope is provided by kotlinx.coroutines as a convenient way to launch coroutines, without the need to have a proper scope.

But using GlobalScope may easily result in leaking coroutines continuing to be executed even after the end of life of the component which started the coroutine.

The recommended approach is to define scopes either making classes implement CoroutineScope or by useing coroutineScope in suspending function.

Snippet of non-compliant Code

Class with defined life-cycle:

class ViewComponent {
	init {
		GlobalScope.launch(Dispatchers.Main) {
			while(isActive) { updateUI() }
		}
	}
}

Suspending function:

suspend fun parallelDecomposition(): Result {
	val part1 = GlobalScope.async { computePart1() }
	val part2 = GlobalScope.async { computePart2() }
	
	return combine(part1.await(), part2.await())
}

Snippet of Compliant Code (fixing the above non-compliant code)

Class with defined life-cycle:

class ViewComponent : CoroutineScope {
	private val job = Job()
	override val coroutineContext = job + Dispatchers.Main
	
	init {
		launch {
			while(isActive) { updateUI() }
		}
	}
	
	fun dispose() {
		job.cancel()
	}
}

Suspending function:

suspend fun parallelDecomposition(): Result = coroutineScope {
	val part1 = async { computePart1() }
	val part2 = async { computePart2() }
	
	return combine(part1.await(), part2.await())
}

External references

Official kotlin coroutine guide (https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/basics.md#structured-concurrency) states:

When we use GlobalScope.launch we create a top-level coroutine. Even though it is light-weight, it still consumes some memory resources while it runs. If we forget to keep a reference to the newly launched coroutine it still runs. […] Instead of launching coroutines in the GlobalScope, […] we can launch coroutines in the specific scope of the operation we are performing.

Here is also a good article about structured concurrency written by Roman Elizarov (maintener of kotlinx.coroutines): https://medium.com/@elizarov/structured-concurrency-722d765aa952

Type : Bug

Using GlobalScope may result in memory leaks.

I would personally set the default severity to “Major”

Tags

performance, multi-threading, bad-practice, design