or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

channels.mdcoroutine-builders.mdcoroutine-management.mddispatchers.mderror-handling.mdflow-api.mdindex.mdselect-expression.mdsynchronization.md

error-handling.mddocs/

0

# Error Handling & Cancellation

1

2

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

3

4

## Capabilities

5

6

### Cancellation

7

8

Cooperative cancellation mechanism for coroutines.

9

10

```kotlin { .api }

11

/**

12

* Base exception for coroutine cancellation

13

*/

14

open class CancellationException : IllegalStateException {

15

constructor()

16

constructor(message: String?)

17

constructor(message: String?, cause: Throwable?)

18

}

19

20

/**

21

* Check if current coroutine is active

22

*/

23

val CoroutineScope.isActive: Boolean

24

25

/**

26

* Ensure coroutine is active, throw CancellationException if cancelled

27

*/

28

fun CoroutineScope.ensureActive()

29

30

/**

31

* Yield to other coroutines and check for cancellation

32

*/

33

suspend fun yield()

34

```

35

36

**Usage Examples:**

37

38

```kotlin

39

import kotlinx.coroutines.*

40

41

suspend fun cancellableWork() {

42

repeat(1000) { i ->

43

// Check for cancellation periodically

44

ensureActive()

45

46

// Or use yield() which also checks cancellation

47

yield()

48

49

// Perform work

50

processItem(i)

51

}

52

}

53

54

// Cancelling coroutines

55

val job = launch {

56

try {

57

cancellableWork()

58

} catch (e: CancellationException) {

59

println("Work was cancelled")

60

// Don't suppress CancellationException

61

throw e

62

}

63

}

64

65

// Cancel after delay

66

delay(100)

67

job.cancel("Timeout reached")

68

```

69

70

### Timeout Operations

71

72

Functions for adding timeouts to coroutine operations.

73

74

```kotlin { .api }

75

/**

76

* Execute block with timeout, throw TimeoutCancellationException on timeout

77

*/

78

suspend fun <T> withTimeout(timeoutMillis: Long, block: suspend CoroutineScope.() -> T): T

79

80

/**

81

* Execute block with timeout, return null on timeout

82

*/

83

suspend fun <T> withTimeoutOrNull(timeoutMillis: Long, block: suspend CoroutineScope.() -> T): T?

84

85

/**

86

* Exception thrown by withTimeout

87

*/

88

class TimeoutCancellationException : CancellationException {

89

val coroutine: Job?

90

}

91

```

92

93

**Usage Examples:**

94

95

```kotlin

96

import kotlinx.coroutines.*

97

98

// Timeout with exception

99

try {

100

val result = withTimeout(1000) {

101

longRunningOperation()

102

}

103

println("Completed: $result")

104

} catch (e: TimeoutCancellationException) {

105

println("Operation timed out")

106

}

107

108

// Timeout with null return

109

val result = withTimeoutOrNull(1000) {

110

longRunningOperation()

111

}

112

113

if (result != null) {

114

println("Completed: $result")

115

} else {

116

println("Operation timed out")

117

}

118

119

// Network request with timeout

120

suspend fun fetchDataWithTimeout(url: String): String? {

121

return withTimeoutOrNull(5000) {

122

httpClient.get(url)

123

}

124

}

125

```

126

127

### Exception Handling

128

129

Structured exception handling in coroutines.

130

131

```kotlin { .api }

132

/**

133

* Exception handler for coroutines

134

*/

135

interface CoroutineExceptionHandler : CoroutineContext.Element {

136

/**

137

* Handle uncaught exception

138

*/

139

fun handleException(context: CoroutineContext, exception: Throwable)

140

141

companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

142

}

143

144

/**

145

* Create exception handler

146

*/

147

fun CoroutineExceptionHandler(handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler

148

```

149

150

**Usage Examples:**

151

152

```kotlin

153

import kotlinx.coroutines.*

154

155

// Global exception handler

156

val handler = CoroutineExceptionHandler { _, exception ->

157

println("Caught exception: ${exception.localizedMessage}")

158

}

159

160

// Use with launch (only works with root coroutines)

161

val scope = CoroutineScope(SupervisorJob() + handler)

162

163

scope.launch {

164

throw RuntimeException("Something went wrong")

165

}

166

167

// Exception handling in async

168

val deferred = async {

169

throw RuntimeException("Async error")

170

}

171

172

try {

173

deferred.await() // Exception thrown here

174

} catch (e: RuntimeException) {

175

println("Caught async exception: $e")

176

}

177

```

178

179

### Supervisor Jobs

180

181

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

182

183

```kotlin { .api }

184

/**

185

* Create supervisor job where child failures don't affect siblings

186

*/

187

fun SupervisorJob(parent: Job? = null): CompletableJob

188

189

/**

190

* Create supervisor scope

191

*/

192

suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R

193

```

194

195

**Usage Examples:**

196

197

```kotlin

198

import kotlinx.coroutines.*

199

200

// Supervisor job - child failures don't cancel siblings

201

val supervisorJob = SupervisorJob()

202

val scope = CoroutineScope(supervisorJob + Dispatchers.Default)

203

204

// These jobs are independent

205

scope.launch {

206

delay(100)

207

throw RuntimeException("Job 1 failed")

208

}

209

210

scope.launch {

211

delay(200)

212

println("Job 2 completed successfully") // Still runs

213

}

214

215

// Supervisor scope

216

supervisorScope {

217

launch {

218

throw RuntimeException("Child 1 failed")

219

}

220

221

launch {

222

delay(100)

223

println("Child 2 completed") // Still completes

224

}

225

226

println("Supervisor scope completed")

227

}

228

```

229

230

### Non-Cancellable Context

231

232

Context that prevents cancellation for cleanup operations.

233

234

```kotlin { .api }

235

/**

236

* Non-cancellable job for cleanup operations

237

*/

238

object NonCancellable : CoroutineContext.Element, Job {

239

override val isActive: Boolean get() = true

240

override val isCompleted: Boolean get() = false

241

override val isCancelled: Boolean get() = false

242

// ... other Job methods are no-ops

243

}

244

```

245

246

**Usage Examples:**

247

248

```kotlin

249

import kotlinx.coroutines.*

250

251

suspend fun operationWithCleanup() {

252

try {

253

// Cancellable operation

254

performWork()

255

} finally {

256

// Non-cancellable cleanup

257

withContext(NonCancellable) {

258

// This cleanup code will run even if coroutine is cancelled

259

releaseResources()

260

saveState()

261

}

262

}

263

}

264

265

// File operations with guaranteed cleanup

266

suspend fun processFile(filename: String) {

267

val file = openFile(filename)

268

try {

269

processFileContent(file)

270

} finally {

271

withContext(NonCancellable) {

272

// Always close file, even if cancelled

273

file.close()

274

}

275

}

276

}

277

```

278

279

## Exception Handling Patterns

280

281

### Try-Catch in Coroutines

282

283

Normal exception handling works in coroutines:

284

285

```kotlin

286

suspend fun handleExceptions() {

287

try {

288

val result = riskyOperation()

289

processResult(result)

290

} catch (e: NetworkException) {

291

handleNetworkError(e)

292

} catch (e: ParseException) {

293

handleParseError(e)

294

} finally {

295

cleanup()

296

}

297

}

298

```

299

300

### Async Error Handling

301

302

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

303

304

```kotlin

305

suspend fun asyncErrorHandling() {

306

val deferred1 = async { mayFailOperation1() }

307

val deferred2 = async { mayFailOperation2() }

308

309

// Exceptions thrown here when awaited

310

try {

311

val result1 = deferred1.await()

312

val result2 = deferred2.await()

313

processResults(result1, result2)

314

} catch (e: Exception) {

315

handleError(e)

316

}

317

}

318

319

// Multiple async with individual error handling

320

suspend fun individualAsyncErrors() {

321

val deferred1 = async { mayFailOperation1() }

322

val deferred2 = async { mayFailOperation2() }

323

324

val result1 = try {

325

deferred1.await()

326

} catch (e: Exception) {

327

handleError1(e)

328

null

329

}

330

331

val result2 = try {

332

deferred2.await()

333

} catch (e: Exception) {

334

handleError2(e)

335

null

336

}

337

338

processResults(result1, result2)

339

}

340

```

341

342

### Launch Error Handling

343

344

Errors in launch propagate to parent unless handled:

345

346

```kotlin

347

// Unhandled exceptions crash the parent

348

launch {

349

throw RuntimeException("This will crash parent")

350

}

351

352

// Handle exceptions in launch

353

launch {

354

try {

355

riskyOperation()

356

} catch (e: Exception) {

357

handleError(e)

358

}

359

}

360

361

// Or use exception handler

362

val handler = CoroutineExceptionHandler { _, exception ->

363

handleGlobalError(exception)

364

}

365

366

launch(handler) {

367

throw RuntimeException("Handled by exception handler")

368

}

369

```

370

371

## Advanced Error Handling

372

373

### Retry Logic

374

375

Implementing retry patterns with exponential backoff:

376

377

```kotlin

378

suspend fun <T> retryWithBackoff(

379

times: Int = 3,

380

initialDelay: Long = 100,

381

maxDelay: Long = 1000,

382

factor: Double = 2.0,

383

block: suspend () -> T

384

): T {

385

var currentDelay = initialDelay

386

repeat(times - 1) {

387

try {

388

return block()

389

} catch (e: Exception) {

390

delay(currentDelay)

391

currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)

392

}

393

}

394

return block() // Last attempt

395

}

396

397

// Usage

398

val result = retryWithBackoff(times = 3) {

399

fetchDataFromServer()

400

}

401

```

402

403

### Circuit Breaker Pattern

404

405

```kotlin

406

class CircuitBreaker(

407

private val failureThreshold: Int = 5,

408

private val resetTimeoutMs: Long = 60000

409

) {

410

private var failures = 0

411

private var lastFailureTime = 0L

412

private var state = State.CLOSED

413

414

enum class State { CLOSED, OPEN, HALF_OPEN }

415

416

suspend fun <T> execute(block: suspend () -> T): T {

417

when (state) {

418

State.OPEN -> {

419

if (System.currentTimeMillis() - lastFailureTime > resetTimeoutMs) {

420

state = State.HALF_OPEN

421

} else {

422

throw CircuitBreakerOpenException()

423

}

424

}

425

State.HALF_OPEN -> {

426

// Allow one test call

427

}

428

State.CLOSED -> {

429

// Normal operation

430

}

431

}

432

433

return try {

434

val result = block()

435

onSuccess()

436

result

437

} catch (e: Exception) {

438

onFailure()

439

throw e

440

}

441

}

442

443

private fun onSuccess() {

444

failures = 0

445

state = State.CLOSED

446

}

447

448

private fun onFailure() {

449

failures++

450

lastFailureTime = System.currentTimeMillis()

451

if (failures >= failureThreshold) {

452

state = State.OPEN

453

}

454

}

455

}

456

```

457

458

## Best Practices

459

460

### 1. Don't Suppress CancellationException

461

462

```kotlin

463

// Wrong - suppresses cancellation

464

try {

465

work()

466

} catch (e: Exception) {

467

// This catches CancellationException too!

468

log.error("Error", e)

469

}

470

471

// Correct - let cancellation propagate

472

try {

473

work()

474

} catch (e: CancellationException) {

475

throw e // Re-throw cancellation

476

} catch (e: Exception) {

477

log.error("Error", e)

478

}

479

480

// Or be specific about what you catch

481

try {

482

work()

483

} catch (e: NetworkException) {

484

handleNetworkError(e)

485

}

486

```

487

488

### 2. Use Supervisor Scope for Independent Tasks

489

490

```kotlin

491

// Good - independent tasks

492

supervisorScope {

493

launch { backgroundTask1() }

494

launch { backgroundTask2() }

495

launch { backgroundTask3() }

496

}

497

498

// Bad - one failure cancels all

499

coroutineScope {

500

launch { backgroundTask1() }

501

launch { backgroundTask2() }

502

launch { backgroundTask3() }

503

}

504

```

505

506

### 3. Handle Timeouts Appropriately

507

508

```kotlin

509

// For operations that should timeout

510

val result = withTimeoutOrNull(5000) {

511

networkCall()

512

}

513

514

if (result == null) {

515

// Handle timeout gracefully

516

showTimeoutMessage()

517

}

518

519

// For critical operations

520

try {

521

withTimeout(30000) {

522

criticalDatabaseUpdate()

523

}

524

} catch (e: TimeoutCancellationException) {

525

// Log and potentially retry

526

logger.error("Critical operation timed out", e)

527

throw e

528

}

529

```

530

531

## Additional Exception Classes

532

533

### Channel-Related Exceptions

534

535

Exceptions specific to channel operations.

536

537

```kotlin { .api }

538

/**

539

* Exception thrown when attempting to send on a closed channel

540

*/

541

class ClosedSendChannelException(message: String? = null) : IllegalStateException(message)

542

543

/**

544

* Exception thrown when attempting to receive from a closed channel

545

*/

546

class ClosedReceiveChannelException(message: String? = null) : NoSuchElementException(message)

547

```

548

549

### Completion Handler Exceptions

550

551

Exceptions related to completion handler execution.

552

553

```kotlin { .api }

554

/**

555

* Exception thrown when a completion handler itself throws an exception

556

*/

557

class CompletionHandlerException(

558

message: String,

559

cause: Throwable

560

) : RuntimeException(message, cause)

561

```

562

563

**Usage Context:**

564

565

```kotlin

566

import kotlinx.coroutines.*

567

import kotlinx.coroutines.channels.*

568

569

// Channel exception handling

570

suspend fun handleChannelExceptions() {

571

val channel = Channel<String>()

572

channel.close()

573

574

try {

575

channel.send("message")

576

} catch (e: ClosedSendChannelException) {

577

println("Cannot send to closed channel: ${e.message}")

578

}

579

580

try {

581

val message = channel.receive()

582

} catch (e: ClosedReceiveChannelException) {

583

println("Cannot receive from closed channel: ${e.message}")

584

}

585

}

586

587

// Completion handler exception handling

588

fun handleCompletionExceptions() {

589

val job = Job()

590

591

job.invokeOnCompletion { cause ->

592

// If this handler throws, it wraps in CompletionHandlerException

593

throw RuntimeException("Handler error")

594

}

595

596

job.complete()

597

}

598

```