Concevoir des architectures de coroutines résilientes
Combinez supervisorScope, une logique de nouvelle tentative et des gestionnaires pour obtenir une concurrence adaptée à la production.
Concevoir des architectures de coroutines résilientes est une leçon Kotlin Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Kotlin Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Kotlin Academy comprend 4 leçons au total.
Qu'est-ce qui rend les coroutines résilientes ?
Les architectures de coroutines résilientes gèrent les échecs avec élégance : elles isolent les échecs, réessaient les erreurs passagères, annulent le travail obsolète et libèrent les ressources de manière déterministe.
import kotlinx.coroutines.*
// Resilience pillars:
// 1. Failure isolation (SupervisorJob)
// 2. Retry with backoff (retry operator / loop)
// 3. Timeout guards (withTimeout)
// 4. Clean teardown (finally + NonCancellable)
// 5. Observability (CoroutineExceptionHandler)
fun main() = runBlocking { println("Design for failure from the start") }Modèle de portée au niveau de l'application
Créez une seule CoroutineScope au niveau de l'application avec SupervisorJob + handler. Injectez-la dans les services afin qu'ils partagent tous le même cycle de vie.
import kotlinx.coroutines.*
object AppCoroutineScope {
private val handler = CoroutineExceptionHandler { _, e ->
println("[AppScope] Uncaught: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
fun cancel() = scope.cancel()
}Nouvelle tentative avec temporisation exponentielle
Entourez les appels réseau d'une boucle de nouvelles tentatives avec une temporisation exponentielle afin de gérer élégamment les échecs passagers.
import kotlinx.coroutines.*
suspend fun <T> retryWithBackoff(
times: Int = 3,
initialDelay: Long = 100,
block: suspend () -> T
): T {
var delay = initialDelay
repeat(times - 1) { attempt ->
try { return block() }
catch (e: Exception) {
if (e is CancellationException) throw e
println("Attempt ${attempt+1} failed, retrying in ${delay}ms")
delay(delay)
delay *= 2
}
}
return block()
}
fun main() = runBlocking {
var n = 0
val result = retryWithBackoff {
if (n++ < 2) throw RuntimeException("transient")
"success"
}
println(result)
}Modèle de disjoncteur
Un disjoncteur cesse d'appeler un service défaillant après N échecs et se réactive après une période de refroidissement, ce qui évite les échecs en cascade.
import kotlinx.coroutines.*
class CircuitBreaker(val maxFailures: Int, val cooldownMs: Long) {
private var failures = 0
private var openUntil = 0L
suspend fun <T> call(block: suspend () -> T): T {
if (System.currentTimeMillis() < openUntil) throw RuntimeException("Circuit open")
return try {
val result = block()
failures = 0
result
} catch (e: Exception) {
if (e is CancellationException) throw e
if (++failures >= maxFailures) openUntil = System.currentTimeMillis() + cooldownMs
throw e
}
}
}Délai d'attente pour chaque appel externe
Entourez toujours les opérations d'entrée-sortie externes de withTimeout ou withTimeoutOrNull. Ne laissez jamais une connexion bloquée empêcher indéfiniment une coroutine de progresser.
import kotlinx.coroutines.*
suspend fun fetchWithTimeout(url: String): String? = withTimeoutOrNull(3000) {
// ktor: client.get(url).body()
delay(100) // simulate
"response from $url"
}
fun main() = runBlocking {
val result = fetchWithTimeout("https://api.example.com")
println(result ?: "Timed out")
}Portée structurée par fonctionnalité
Créez une CoroutineScope distincte pour chaque fonctionnalité ou écran, avec son propre SupervisorJob. Annulez-la lorsque la fonctionnalité est détruite.
import kotlinx.coroutines.*
class FeatureController {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun start() {
scope.launch { /* load data */ }
scope.launch { /* subscribe to updates */ }
}
fun destroy() = scope.cancel() // cancels all feature coroutines
}Sécurité des ressources avec use()
Utilisez use { } pour les ressources AutoCloseable. Combinez cette approche avec l'annulation des coroutines : les ressources sont fermées même lorsque la coroutine est annulée.
import kotlinx.coroutines.*
class Connection : AutoCloseable {
override fun close() = println("Connection closed")
suspend fun fetch(): String { delay(100); return "data" }
}
fun main() = runBlocking {
val job = launch {
Connection().use { conn ->
println(conn.fetch())
} // close() called even on cancellation
}
delay(50)
job.cancelAndJoin()
}Arrêt progressif
Mettez en œuvre un arrêt progressif : cessez d'accepter du nouveau travail, attendez la fin des coroutines en cours, puis annulez la portée.
import kotlinx.coroutines.*
class WorkQueue {
private val scope = CoroutineScope(SupervisorJob())
private val jobs = mutableListOf<Job>()
fun submit(block: suspend () -> Unit) {
jobs += scope.launch { block() }
}
suspend fun shutdown() {
jobs.forEach { it.join() } // wait for all
scope.cancel() // then cancel scope
}
}
fun main() = runBlocking {
val q = WorkQueue()
repeat(3) { i -> q.submit { delay(50); println("Task $i done") } }
q.shutdown()
println("Queue shut down cleanly")
}Observabilité : CoroutineName
Étiquetez les coroutines avec CoroutineName pour faciliter le débogage. Les noms apparaissent dans les traces de pile et peuvent être utilisés dans les gestionnaires d'exception pour une journalisation structurée.
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { ctx, e ->
println("[${ctx[CoroutineName]?.name}] Failed: ${e.message}")
}
CoroutineScope(SupervisorJob() + handler).apply {
launch(CoroutineName("UserLoader")) { throw RuntimeException("DB error") }
launch(CoroutineName("Analytics")) { delay(100); println("Analytics ok") }
delay(200); cancel()
}
}Évitez GlobalScope
N'utilisez jamais GlobalScope en production. Il crée des coroutines sans parent, sans gestion de cycle de vie et sans annulation structurée. Utilisez plutôt des solutions limitées à une portée.
import kotlinx.coroutines.*
// BAD — GlobalScope leaks coroutines:
// GlobalScope.launch { delay(Long.MAX_VALUE) }
// GOOD — scoped, cancellable:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { delay(100); println("Scoped") }
fun main() = runBlocking { delay(200); scope.cancel() }Tester la résilience
Utilisez runTest avec TestCoroutineScheduler pour simuler les échecs, les délais d'attente et les nouvelles tentatives dans les tests unitaires, sans délais réels.
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun testRetry() = runTest {
var attempts = 0
val result = retryWithBackoff(3, 100) {
if (attempts++ < 2) throw RuntimeException("fail")
"ok"
}
println(result) // "ok"
}
// In test, virtual time advances instantly through delaysVérification rapide
Quelle combinaison constitue la base d'une portée de coroutine résiliente et de longue durée ?
Récapitulatif
Les architectures résilientes combinent SupervisorJob pour l'isolation, retryWithBackoff pour les erreurs passagères, withTimeout pour les opérations d'entrée-sortie bloquées, nettoyage/NonCancellable pour libérer les ressources et CoroutineExceptionHandler pour l'observabilité.
Questions Fréquemment Posées
La leçon « Concevoir des architectures de coroutines résilientes » est-elle gratuite ?
Oui — le texte complet de « Concevoir des architectures de coroutines résilientes » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Kotlin Academy, passe à CoddyKit PRO. Le cours Kotlin Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Concevoir des architectures de coroutines résilientes » ?
Combinez supervisorScope, une logique de nouvelle tentative et des gestionnaires pour obtenir une concurrence adaptée à la production. Tu pratiques Kotlin Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Kotlin Academy ?
Aucune expérience préalable n'est requise. Kotlin Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Concevoir des architectures de coroutines résilientes » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Kotlin Academy ?
Oui. Chaque leçon Kotlin Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- SupervisorJob ou Job : isolation des échecs
- CoroutineExceptionHandler : gestionnaire global des exceptions non interceptées
- Propagation des exceptions avec async/await
- Concevoir des architectures de coroutines résilientes