Create rule S6306: Coroutine usage should adhere to structured concurrency principles (#167)
This commit is contained in:
parent
b011d5c853
commit
81f1f076be
20
rules/S6306/kotlin/metadata.json
Normal file
20
rules/S6306/kotlin/metadata.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "Coroutine usage should adhere to structured concurrency principles",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "1h"
|
||||
},
|
||||
"tags": [
|
||||
"coroutines",
|
||||
"leak",
|
||||
"pitfall",
|
||||
"bad-practice"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-6306",
|
||||
"sqKey": "S6306",
|
||||
"scope": "All",
|
||||
"defaultQualityProfiles": ["Sonar way"]
|
||||
}
|
86
rules/S6306/kotlin/rule.adoc
Normal file
86
rules/S6306/kotlin/rule.adoc
Normal file
@ -0,0 +1,86 @@
|
||||
Kotlin coroutines follow the principle of structured concurrency. This helps in preventing resource leaks and ensures that scopes are only exited once all child coroutines have exited. Hence, structured concurrency enables developers to build concurrent applications while having to worry less about cleaning up concurrent tasks manually.
|
||||
|
||||
It is possible to break this concept of structured concurrency in various ways. Generally, this is not advised, as it can open the door to coroutines being leaked or lost. Ask yourself if breaking structured concurrency here is really necessary for the application's business logic, or if it could be avoided by refactoring parts of the code.
|
||||
|
||||
This rule raises an issue when it detects that the structured concurrency principles are violated. It avoids reporting on valid use cases and in situations where developers have consciously opted into using delicate APIs (e.g. by using the `@OptIn` annotation) and hence should be aware of the possible pitfalls.
|
||||
|
||||
== Noncompliant Code Example
|
||||
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html[GlobalScope]:
|
||||
----
|
||||
fun main() {
|
||||
GlobalScope.launch { // Noncompliant: no explicit opt-in to DelicateCoroutinesApi
|
||||
// Do some work
|
||||
}.join()
|
||||
}
|
||||
----
|
||||
|
||||
Manual job instantiation:
|
||||
----
|
||||
fun startLongRunningBackgroundJob(job: Job) {
|
||||
val coroutineScope = CoroutineScope(job)
|
||||
coroutineScope.launch(Job()) { // Noncompliant: new job instance passed to launch()
|
||||
// Do some work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Manual supervisor instantiation:
|
||||
----
|
||||
coroutineScope {
|
||||
launch(SupervisorJob()) { // Noncompliant: new supervisor instance passed to launch()
|
||||
// Do some work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Compliant Solution
|
||||
In many situations, a good pattern is to use `coroutineScope` as provided in suspending functions:
|
||||
----
|
||||
suspend fun main() {
|
||||
worker()
|
||||
}
|
||||
|
||||
suspend fun worker() {
|
||||
coroutineScope {
|
||||
launch { // Compliant: no manually created job/supervisor instance passed to launch()
|
||||
// Do some work
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html[GlobalScope]:
|
||||
----
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun main() {
|
||||
GlobalScope.launch { // Compliant: explicit opt-in to DelicateCoroutinesApi via method annotation
|
||||
// Do some work
|
||||
}.join()
|
||||
}
|
||||
----
|
||||
|
||||
No manual job instantiation:
|
||||
----
|
||||
fun startLongRunningBackgroundJob(job: Job) {
|
||||
val coroutineScope = CoroutineScope(job)
|
||||
coroutineScope.launch { // Compliant: no manually created job/supervisor instance passed to launch()
|
||||
// Do some work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Using a supervisor scope instead of manually instantiating a supervisor:
|
||||
----
|
||||
supervisorScope {
|
||||
launch {
|
||||
// Do some work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== See
|
||||
|
||||
* https://kotlinlang.org/docs/coroutines-basics.html#structured-concurrency[Structured concurrency] in the Kotlin docs
|
||||
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html[GlobalScope documentation]
|
||||
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html[coroutineScope documentation]
|
||||
* https://developer.android.com/kotlin/coroutines/coroutines-best-practices[Android coroutines best practices]
|
2
rules/S6306/metadata.json
Normal file
2
rules/S6306/metadata.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user