Last active
December 5, 2025 08:54
-
-
Save vitalikas/8080de296e15d8be9430ffe95505a30f to your computer and use it in GitHub Desktop.
SupervisorJob vs Regular Job - Complete Demonstration (FAST VERSION)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * SupervisorJob vs Regular Job - Complete Demonstration (FAST VERSION) | |
| * | |
| * This version uses shorter delays (100ms instead of 3-5 seconds) for online playgrounds | |
| * | |
| * Run in Kotlin Playground: https://play.kotlinlang.org/ | |
| */ | |
| import kotlinx.coroutines.* | |
| // Helper: Coroutine Exception Handler | |
| val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> | |
| println(" 🛡️ CoroutineExceptionHandler caught: ${exception.message}") | |
| } | |
| // Helper: Simulate a task that fails (FAST - 100ms instead of 3s) | |
| suspend fun checkNotifications() { | |
| println(" ⏰ Checking notifications...") | |
| delay(100) // Short delay for demo | |
| println(" 💥 Notification check - THROWING EXCEPTION!") | |
| throw Exception("Notification check failed") | |
| } | |
| // Helper: Simulate a task that succeeds (FAST - 200ms instead of 5s) | |
| suspend fun updateDailyNews() { | |
| println(" 📰 Updating daily news...") | |
| delay(200) // Short delay for demo | |
| println(" ✅ Daily news updated successfully!") | |
| } | |
| fun main() = runBlocking { | |
| println("=".repeat(70)) | |
| println("SupervisorJob vs Regular Job - Complete Demonstration") | |
| println("=".repeat(70)) | |
| println() | |
| println( | |
| """ | |
| 🎯 THE FUNDAMENTAL RULE: "Job is NOT inherited by children" | |
| What this means: | |
| - When you create CoroutineScope(SupervisorJob), it has SupervisorJob-A | |
| - When you call scope.launch(), it creates a NEW Job (Job-B) | |
| - Job-B is ALWAYS a regular Job (not SupervisorJob), with Job-A as its parent | |
| - Job-A is used as a PARENT reference, not copied/inherited | |
| Why this matters: | |
| - Only DIRECT children of SupervisorJob get supervisor isolation | |
| - Grandchildren are under a regular Job → normal cancellation behavior | |
| """.trimIndent()) | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // ❌ Scenario 1: Without SupervisorJob (regular Job) | |
| println("--- Scenario 1: Regular Job (WRONG) ---") | |
| println("Structure: scope with Job() → launch { task1() }; launch { task2() }") | |
| println() | |
| val regularScope = CoroutineScope(Dispatchers.Default + Job()) | |
| regularScope.launch(coroutineExceptionHandler) { | |
| checkNotifications() // Throws exception after 100ms | |
| } | |
| regularScope.launch { | |
| updateDailyNews() // This will be CANCELLED when first coroutine fails! | |
| } | |
| delay(300) // Wait for tasks | |
| println("Regular Job scope status: ${if (regularScope.isActive) "ACTIVE" else "CANCELLED"}") | |
| println("Result: ❌ updateDailyNews was CANCELLED") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // ❌ Scenario 2: SupervisorJob but WRONG structure (sequential in same coroutine) | |
| println("--- Scenario 2: Sequential in Same Coroutine (WRONG) ---") | |
| println("Structure: scope.launch { task1(); task2() }") | |
| println() | |
| val scopeSequential = CoroutineScope(Dispatchers.Default + SupervisorJob()) | |
| scopeSequential.launch(coroutineExceptionHandler) { | |
| checkNotifications() // Throws exception after 100ms | |
| updateDailyNews() // ❌ NEVER RUNS - not reached because exception thrown first | |
| } | |
| delay(300) // Wait for tasks | |
| println("Sequential scope status: ${if (scopeSequential.isActive) "ACTIVE" else "CANCELLED"}") | |
| println("Result: ❌ updateDailyNews never started (sequential execution)") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // ❌ Scenario 3: SupervisorJob with NESTED launches (WRONG - Grandchildren problem!) | |
| println("--- Scenario 3: Nested Launches - Grandchildren Problem (WRONG) ---") | |
| println("Structure: scope.launch { launch { task1() }; launch { task2() } }") | |
| println() | |
| println( | |
| """ | |
| Why this fails: | |
| 1. scope.launch() creates Job-B (parent: SupervisorJob-A) | |
| 2. Inner launch() calls create Job-C and Job-D (parent: Job-B) | |
| 3. Job-C and Job-D are GRANDCHILDREN of SupervisorJob-A | |
| 4. When Job-C fails → Job-B fails → Job-D cancelled | |
| Visual: | |
| SupervisorJob-A | |
| └─ Job-B (regular Job) | |
| ├─ Job-C: checkNotifications() ← fails | |
| └─ Job-D: updateDailyNews() ← CANCELLED | |
| """.trimIndent()) | |
| println() | |
| val scopeNested = CoroutineScope(Dispatchers.Default + SupervisorJob()) | |
| scopeNested.launch(coroutineExceptionHandler) { | |
| launch { checkNotifications() } // Inner Child 1 - throws exception | |
| launch { updateDailyNews() } // Inner Child 2 - GETS CANCELLED | |
| } | |
| delay(300) // Wait for tasks | |
| println("Nested scope status: ${if (scopeNested.isActive) "ACTIVE" else "CANCELLED"}") | |
| println("Result: ❌ updateDailyNews was CANCELLED (grandchildren don't get benefits)") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // ✅ Scenario 4: Direct children of SupervisorJob (BEST PRACTICE) | |
| println("--- Scenario 4: Direct Children of SupervisorJob (BEST PRACTICE) ✅ ---") | |
| println("Structure: scope.launch { task1() }; scope.launch { task2() }") | |
| println() | |
| println( | |
| """ | |
| Why this works: | |
| 1. Both launch() calls are DIRECT children of SupervisorJob-A | |
| 2. When one child fails, SupervisorJob isolates the failure | |
| 3. Other children continue running independently | |
| Visual: | |
| SupervisorJob-A | |
| ├─ Job-C: checkNotifications() ← fails (isolated) | |
| └─ Job-B: updateDailyNews() ← CONTINUES ✅ | |
| """.trimIndent()) | |
| println() | |
| val scopeDirectChildren = CoroutineScope(Dispatchers.Default + SupervisorJob()) | |
| scopeDirectChildren.launch(coroutineExceptionHandler) { | |
| checkNotifications() // Direct child of SupervisorJob-A | |
| } | |
| scopeDirectChildren.launch { | |
| updateDailyNews() // Direct child of SupervisorJob-A | |
| } | |
| delay(300) // Wait for tasks | |
| println("Direct children scope status: ${if (scopeDirectChildren.isActive) "ACTIVE" else "CANCELLED"}") | |
| println("Result: ✅ updateDailyNews COMPLETED! SupervisorJob protected its direct children!") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // ✅ Scenario 5: SupervisorJob with supervisorScope (CORRECT but more complex) | |
| println("--- Scenario 5: Using supervisorScope (CORRECT but more complex) ---") | |
| println("Structure: scope.launch { supervisorScope { launch { task1() }; launch { task2() } } }") | |
| println() | |
| println( | |
| """ | |
| How supervisorScope fixes nesting: | |
| - supervisorScope creates a NEW SupervisorJob for its children | |
| - Inner launches become direct children of this new SupervisorJob | |
| - Failure isolation works again! | |
| """.trimIndent()) | |
| println() | |
| val scopeSupervisorScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) | |
| scopeSupervisorScope.launch { | |
| supervisorScope { | |
| launch(coroutineExceptionHandler) { checkNotifications() } | |
| launch { updateDailyNews() } | |
| } | |
| } | |
| delay(300) // Wait for tasks | |
| println("supervisorScope status: ${if (scopeSupervisorScope.isActive) "ACTIVE" else "CANCELLED"}") | |
| println("Result: ✅ updateDailyNews COMPLETED! supervisorScope isolated the failure!") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| // Final Summary | |
| println("📝 FINAL SUMMARY:") | |
| println() | |
| println("| Scenario | Description | Result |") | |
| println("|----------|--------------------------------|-------------------------------------|") | |
| println("| 1 | Regular Job | ❌ Sibling cancelled |") | |
| println("| 2 | Sequential in same coroutine | ❌ Never reached |") | |
| println("| 3 | Nested launches (grandchildren)| ❌ Grandchildren cancelled |") | |
| println("| 4 | Direct children | ✅ PROTECTED by SupervisorJob BEST! |") | |
| println("| 5 | supervisorScope | ✅ Works but more complex |") | |
| println() | |
| println("=".repeat(70)) | |
| println() | |
| println("🎯 KEY TAKEAWAY:") | |
| println("Only DIRECT children of SupervisorJob get supervisor benefits!") | |
| println() | |
| println("💡 BEST PRACTICE (Scenario 4):") | |
| println( | |
| """ | |
| val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) | |
| scope.launch(exceptionHandler) { | |
| checkNotifications() // Direct child - can fail independently | |
| } | |
| scope.launch { | |
| updateDailyNews() // Direct child - continues even if other fails | |
| } | |
| """.trimIndent()) | |
| println() | |
| println("=".repeat(70)) | |
| println("Demonstration complete!") | |
| println("=".repeat(70)) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment