CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlinx--kotlinx-coroutines-core-iosx64

Kotlin coroutines library providing comprehensive asynchronous programming support with structured concurrency for iOS x64 platforms

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling & Cancellation

Cancellation propagation, exception handling, and timeout management for robust coroutine applications. kotlinx-coroutines provides structured error handling that automatically propagates cancellation and manages exceptions.

Capabilities

Cancellation

Cooperative cancellation mechanism for coroutines.

/**
 * Base exception for coroutine cancellation
 */
open class CancellationException : IllegalStateException {
    constructor()
    constructor(message: String?)
    constructor(message: String?, cause: Throwable?)
}

/**
 * Check if current coroutine is active
 */
val CoroutineScope.isActive: Boolean

/**
 * Ensure coroutine is active, throw CancellationException if cancelled
 */
fun CoroutineScope.ensureActive()

/**
 * Yield to other coroutines and check for cancellation
 */
suspend fun yield()

Usage Examples:

import kotlinx.coroutines.*

suspend fun cancellableWork() {
    repeat(1000) { i ->
        // Check for cancellation periodically
        ensureActive()
        
        // Or use yield() which also checks cancellation
        yield()
        
        // Perform work
        processItem(i)
    }
}

// Cancelling coroutines
val job = launch {
    try {
        cancellableWork()
    } catch (e: CancellationException) {
        println("Work was cancelled")
        // Don't suppress CancellationException
        throw e
    }
}

// Cancel after delay
delay(100)
job.cancel("Timeout reached")

Timeout Operations

Functions for adding timeouts to coroutine operations.

/**
 * Execute block with timeout, throw TimeoutCancellationException on timeout
 */
suspend fun <T> withTimeout(timeoutMillis: Long, block: suspend CoroutineScope.() -> T): T

/**
 * Execute block with timeout, return null on timeout
 */
suspend fun <T> withTimeoutOrNull(timeoutMillis: Long, block: suspend CoroutineScope.() -> T): T?

/**
 * Exception thrown by withTimeout
 */
class TimeoutCancellationException : CancellationException {
    val coroutine: Job?
}

Usage Examples:

import kotlinx.coroutines.*

// Timeout with exception
try {
    val result = withTimeout(1000) {
        longRunningOperation()
    }
    println("Completed: $result")
} catch (e: TimeoutCancellationException) {
    println("Operation timed out")
}

// Timeout with null return
val result = withTimeoutOrNull(1000) {
    longRunningOperation()
}

if (result != null) {
    println("Completed: $result")
} else {
    println("Operation timed out")
}

// Network request with timeout
suspend fun fetchDataWithTimeout(url: String): String? {
    return withTimeoutOrNull(5000) {
        httpClient.get(url)
    }
}

Exception Handling

Structured exception handling in coroutines.

/**
 * Exception handler for coroutines
 */
interface CoroutineExceptionHandler : CoroutineContext.Element {
    /**
     * Handle uncaught exception
     */
    fun handleException(context: CoroutineContext, exception: Throwable)
    
    companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
}

/**
 * Create exception handler
 */
fun CoroutineExceptionHandler(handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler

Usage Examples:

import kotlinx.coroutines.*

// Global exception handler
val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught exception: ${exception.localizedMessage}")
}

// Use with launch (only works with root coroutines)
val scope = CoroutineScope(SupervisorJob() + handler)

scope.launch {
    throw RuntimeException("Something went wrong")
}

// Exception handling in async
val deferred = async {
    throw RuntimeException("Async error")
}

try {
    deferred.await() // Exception thrown here
} catch (e: RuntimeException) {
    println("Caught async exception: $e")
}

Supervisor Jobs

Jobs that don't cancel children when one child fails.

/**
 * Create supervisor job where child failures don't affect siblings
 */
fun SupervisorJob(parent: Job? = null): CompletableJob

/**
 * Create supervisor scope
 */
suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R

Usage Examples:

import kotlinx.coroutines.*

// Supervisor job - child failures don't cancel siblings
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(supervisorJob + Dispatchers.Default)

// These jobs are independent
scope.launch {
    delay(100)
    throw RuntimeException("Job 1 failed")
}

scope.launch {
    delay(200)
    println("Job 2 completed successfully") // Still runs
}

// Supervisor scope
supervisorScope {
    launch {
        throw RuntimeException("Child 1 failed")
    }
    
    launch {
        delay(100)
        println("Child 2 completed") // Still completes
    }
    
    println("Supervisor scope completed")
}

Non-Cancellable Context

Context that prevents cancellation for cleanup operations.

/**
 * Non-cancellable job for cleanup operations
 */
object NonCancellable : CoroutineContext.Element, Job {
    override val isActive: Boolean get() = true
    override val isCompleted: Boolean get() = false
    override val isCancelled: Boolean get() = false
    // ... other Job methods are no-ops
}

Usage Examples:

import kotlinx.coroutines.*

suspend fun operationWithCleanup() {
    try {
        // Cancellable operation
        performWork()
    } finally {
        // Non-cancellable cleanup
        withContext(NonCancellable) {
            // This cleanup code will run even if coroutine is cancelled
            releaseResources()
            saveState()
        }
    }
}

// File operations with guaranteed cleanup
suspend fun processFile(filename: String) {
    val file = openFile(filename)
    try {
        processFileContent(file)
    } finally {
        withContext(NonCancellable) {
            // Always close file, even if cancelled
            file.close()
        }
    }
}

Exception Handling Patterns

Try-Catch in Coroutines

Normal exception handling works in coroutines:

suspend fun handleExceptions() {
    try {
        val result = riskyOperation()
        processResult(result)
    } catch (e: NetworkException) {
        handleNetworkError(e)
    } catch (e: ParseException) {
        handleParseError(e)
    } finally {
        cleanup()
    }
}

Async Error Handling

Exceptions in async are stored until await() is called:

suspend fun asyncErrorHandling() {
    val deferred1 = async { mayFailOperation1() }
    val deferred2 = async { mayFailOperation2() }
    
    // Exceptions thrown here when awaited
    try {
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        processResults(result1, result2)
    } catch (e: Exception) {
        handleError(e)
    }
}

// Multiple async with individual error handling
suspend fun individualAsyncErrors() {
    val deferred1 = async { mayFailOperation1() }
    val deferred2 = async { mayFailOperation2() }
    
    val result1 = try {
        deferred1.await()
    } catch (e: Exception) {
        handleError1(e)
        null
    }
    
    val result2 = try {
        deferred2.await()
    } catch (e: Exception) {
        handleError2(e)
        null
    }
    
    processResults(result1, result2)
}

Launch Error Handling

Errors in launch propagate to parent unless handled:

// Unhandled exceptions crash the parent
launch {
    throw RuntimeException("This will crash parent")
}

// Handle exceptions in launch
launch {
    try {
        riskyOperation()
    } catch (e: Exception) {
        handleError(e)
    }
}

// Or use exception handler
val handler = CoroutineExceptionHandler { _, exception ->
    handleGlobalError(exception)
}

launch(handler) {
    throw RuntimeException("Handled by exception handler")
}

Advanced Error Handling

Retry Logic

Implementing retry patterns with exponential backoff:

suspend fun <T> retryWithBackoff(
    times: Int = 3,
    initialDelay: Long = 100,
    maxDelay: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelay
    repeat(times - 1) {
        try {
            return block()
        } catch (e: Exception) {
            delay(currentDelay)
            currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
        }
    }
    return block() // Last attempt
}

// Usage
val result = retryWithBackoff(times = 3) {
    fetchDataFromServer()
}

Circuit Breaker Pattern

class CircuitBreaker(
    private val failureThreshold: Int = 5,
    private val resetTimeoutMs: Long = 60000
) {
    private var failures = 0
    private var lastFailureTime = 0L
    private var state = State.CLOSED
    
    enum class State { CLOSED, OPEN, HALF_OPEN }
    
    suspend fun <T> execute(block: suspend () -> T): T {
        when (state) {
            State.OPEN -> {
                if (System.currentTimeMillis() - lastFailureTime > resetTimeoutMs) {
                    state = State.HALF_OPEN
                } else {
                    throw CircuitBreakerOpenException()
                }
            }
            State.HALF_OPEN -> {
                // Allow one test call
            }
            State.CLOSED -> {
                // Normal operation
            }
        }
        
        return try {
            val result = block()
            onSuccess()
            result
        } catch (e: Exception) {
            onFailure()
            throw e
        }
    }
    
    private fun onSuccess() {
        failures = 0
        state = State.CLOSED
    }
    
    private fun onFailure() {
        failures++
        lastFailureTime = System.currentTimeMillis()
        if (failures >= failureThreshold) {
            state = State.OPEN
        }
    }
}

Best Practices

1. Don't Suppress CancellationException

// Wrong - suppresses cancellation
try {
    work()
} catch (e: Exception) {
    // This catches CancellationException too!
    log.error("Error", e)
}

// Correct - let cancellation propagate
try {
    work()
} catch (e: CancellationException) {
    throw e // Re-throw cancellation
} catch (e: Exception) {
    log.error("Error", e)
}

// Or be specific about what you catch
try {
    work()
} catch (e: NetworkException) {
    handleNetworkError(e)
}

2. Use Supervisor Scope for Independent Tasks

// Good - independent tasks
supervisorScope {
    launch { backgroundTask1() }
    launch { backgroundTask2() }
    launch { backgroundTask3() }
}

// Bad - one failure cancels all
coroutineScope {
    launch { backgroundTask1() }
    launch { backgroundTask2() }
    launch { backgroundTask3() }
}

3. Handle Timeouts Appropriately

// For operations that should timeout
val result = withTimeoutOrNull(5000) {
    networkCall()
}

if (result == null) {
    // Handle timeout gracefully
    showTimeoutMessage()
}

// For critical operations
try {
    withTimeout(30000) {
        criticalDatabaseUpdate()
    }
} catch (e: TimeoutCancellationException) {
    // Log and potentially retry
    logger.error("Critical operation timed out", e)
    throw e
}

Additional Exception Classes

Channel-Related Exceptions

Exceptions specific to channel operations.

/**
 * Exception thrown when attempting to send on a closed channel
 */
class ClosedSendChannelException(message: String? = null) : IllegalStateException(message)

/**
 * Exception thrown when attempting to receive from a closed channel
 */
class ClosedReceiveChannelException(message: String? = null) : NoSuchElementException(message)

Completion Handler Exceptions

Exceptions related to completion handler execution.

/**
 * Exception thrown when a completion handler itself throws an exception
 */
class CompletionHandlerException(
    message: String,
    cause: Throwable
) : RuntimeException(message, cause)

Usage Context:

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

// Channel exception handling
suspend fun handleChannelExceptions() {
    val channel = Channel<String>()
    channel.close()
    
    try {
        channel.send("message")
    } catch (e: ClosedSendChannelException) {
        println("Cannot send to closed channel: ${e.message}")
    }
    
    try {
        val message = channel.receive()
    } catch (e: ClosedReceiveChannelException) {
        println("Cannot receive from closed channel: ${e.message}")
    }
}

// Completion handler exception handling
fun handleCompletionExceptions() {
    val job = Job()
    
    job.invokeOnCompletion { cause ->
        // If this handler throws, it wraps in CompletionHandlerException
        throw RuntimeException("Handler error")
    }
    
    job.complete()
}

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-coroutines-core-iosx64

docs

channels.md

coroutine-builders.md

coroutine-management.md

dispatchers.md

error-handling.md

flow-api.md

index.md

select-expression.md

synchronization.md

tile.json