rspec/rules/S6307/kotlin/rule.adoc

69 lines
3.0 KiB
Plaintext
Raw Normal View History

== Why is this an issue?
Generally speaking, main threads should be available to allow user-facing parts of an application to remain responsive. Long-running blocking operations can significantly reduce threads' availability and are best executed on a designated thread pool.
As a consequence, suspending functions should not block main threads and instead move any long-running blocking tasks off the main thread. This can be done conveniently by using `withContext` with an appropriate dispatcher. Alternatively, coroutine builders such as `launch` and `async` accept an optional `CoroutineContext`. An appropriate dispatcher could be `Dispatchers.IO` for long-running blocking IO operations, which can create and shutdown threads on demand.
For some blocking tasks and APIs there may already be suspending alternatives available. When available, these alternatives should be used instead of their blocking counterparts.
This rule raises an issue when the call of a long-running blocking function is detected within a suspending function without the use of an appropriate dispatcher. If non-blocking alternatives to the called function are known, they may be suggested (e.g. use `delay(...)` instead of `Thread.sleep(...)`).
=== Noncompliant code example
Executing long-running blocking IO operations on the main thread pool:
2022-02-04 17:28:24 +01:00
[source,kotlin]
----
class workerClass {
suspend fun worker(): String {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder(URI("https://example.com")).build()
return coroutineScope {
client.send(request, HttpResponse.BodyHandlers.ofString()).body() // Noncompliant
}
}
}
----
Using inappropriate blocking APIs:
2022-02-04 17:28:24 +01:00
[source,kotlin]
----
suspend fun example() {
...
Thread.sleep(1000) // Noncompliant
...
}
----
=== Compliant solution
Executing long-running blocking IO operations in an appropriate thread pool using `Dispatcher.IO`:
2022-02-04 17:28:24 +01:00
[source,kotlin]
----
class workerClass(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun worker(): String {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder(URI("https://example.com")).build()
return withContext(ioDispatcher) {
client.send(request, HttpResponse.BodyHandlers.ofString()).body() // Compliant
}
}
}
----
Using appropriate non-blocking APIs:
2022-02-04 17:28:24 +01:00
[source,kotlin]
----
suspend fun example() {
...
delay(1000) // Compliant
...
}
----
== Resources
* https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html[Coroutine context and dispatchers]
* https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe[Suspend functions should be safe to call from the main thread] (Android coroutines best practices)
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html[IO CoroutineDispatcher]
* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html[Default CoroutineDispatcher]