0
# Exception Handling
1
2
## Overview
3
4
The Exception Handling capabilities provide WASI-specific implementations for Kotlin's exception system. The implementation includes the base `Throwable` class with limited stack trace support and comprehensive error code mapping for WASI-specific system call failures.
5
6
## API Reference
7
8
### Throwable Base Class
9
10
```kotlin { .api }
11
/**
12
* The base class for all errors and exceptions in WASI environment.
13
* Provides limited stack trace support due to WebAssembly constraints.
14
*/
15
actual class Throwable {
16
/**
17
* Creates a throwable with no message and no cause.
18
*/
19
actual constructor()
20
21
/**
22
* Creates a throwable with the specified message.
23
* @param message the detail message
24
*/
25
actual constructor(message: String?)
26
27
/**
28
* Creates a throwable with the specified cause.
29
* @param cause the cause of this throwable
30
*/
31
actual constructor(cause: Throwable?)
32
33
/**
34
* Creates a throwable with the specified message and cause.
35
* @param message the detail message
36
* @param cause the cause of this throwable
37
*/
38
actual constructor(message: String?, cause: Throwable?)
39
40
/**
41
* The detail message of this throwable.
42
*/
43
actual val message: String?
44
45
/**
46
* The cause of this throwable, or null if the cause is unknown.
47
*/
48
actual val cause: Throwable?
49
}
50
```
51
52
### WASI Error Handling
53
54
```kotlin { .api }
55
/**
56
* Internal WASI error codes from preview1 specification.
57
* Used for mapping WASI system call errors to Kotlin exceptions.
58
*/
59
internal enum class WasiErrorCode {
60
SUCCESS, // No error occurred
61
E2BIG, // Argument list too long
62
EACCES, // Permission denied
63
EADDRINUSE, // Address already in use
64
EADDRNOTAVAIL, // Address not available
65
EAFNOSUPPORT, // Address family not supported
66
EAGAIN, // Resource temporarily unavailable
67
EALREADY, // Connection already in progress
68
EBADF, // Bad file descriptor
69
EBADMSG, // Bad message
70
EBUSY, // Device or resource busy
71
ECANCELED, // Operation canceled
72
ECHILD, // No child processes
73
ECONNABORTED, // Connection aborted
74
ECONNREFUSED, // Connection refused
75
ECONNRESET, // Connection reset
76
EDEADLK, // Resource deadlock avoided
77
EDESTADDRREQ, // Destination address required
78
EDOM, // Mathematics argument out of domain
79
EDQUOT, // Disk quota exceeded
80
EEXIST, // File exists
81
EFAULT, // Bad address
82
EFBIG, // File too large
83
EHOSTUNREACH, // Host is unreachable
84
EIDRM, // Identifier removed
85
EILSEQ, // Invalid or incomplete multibyte sequence
86
EINPROGRESS, // Operation in progress
87
EINTR, // Interrupted system call
88
EINVAL, // Invalid argument
89
EIO, // Input/output error
90
EISCONN, // Socket is connected
91
EISDIR, // Is a directory
92
ELOOP, // Symbolic link loop
93
EMFILE, // Too many open files
94
EMLINK, // Too many links
95
EMSGSIZE, // Message too large
96
EMULTIHOP, // Multihop attempted
97
ENAMETOOLONG, // File name too long
98
ENETDOWN, // Network is down
99
ENETRESET, // Network dropped connection on reset
100
ENETUNREACH, // Network is unreachable
101
ENFILE, // Too many open files in system
102
ENOBUFS, // No buffer space available
103
ENODEV, // No such device
104
ENOENT, // No such file or directory
105
ENOEXEC, // Executable file format error
106
ENOLCK, // No locks available
107
ENOLINK, // Link has been severed
108
ENOMEM, // Cannot allocate memory
109
ENOMSG, // No message of desired type
110
ENOPROTOOPT, // Protocol not available
111
ENOSPC, // No space left on device
112
ENOSYS, // Function not implemented
113
ENOTCONN, // Socket is not connected
114
ENOTDIR, // Not a directory
115
ENOTEMPTY, // Directory not empty
116
ENOTRECOVERABLE, // State not recoverable
117
ENOTSOCK, // Not a socket
118
ENOTSUP, // Operation not supported
119
ENOTTY, // Inappropriate I/O control operation
120
ENXIO, // No such device or address
121
EOVERFLOW, // Value too large for data type
122
EOWNERDEAD, // Previous owner died
123
EPERM, // Operation not permitted
124
EPIPE, // Broken pipe
125
EPROTO, // Protocol error
126
EPROTONOSUPPORT, // Protocol not supported
127
EPROTOTYPE, // Protocol wrong type for socket
128
ERANGE, // Result too large
129
EROFS, // Read-only file system
130
ESPIPE, // Invalid seek
131
ESRCH, // No such process
132
ESTALE, // Stale file handle
133
ETIMEDOUT, // Connection timed out
134
ETXTBSY, // Text file busy
135
EXDEV, // Invalid cross-device link
136
ENOTCAPABLE // Capability insufficient
137
}
138
139
/**
140
* Internal exception class for WASI-specific errors.
141
* Maps WASI error codes to Kotlin exceptions with appropriate messages.
142
*/
143
internal class WasiError(
144
val errorCode: WasiErrorCode,
145
message: String = errorCode.name
146
) : Exception(message)
147
```
148
149
## Usage Examples
150
151
### Basic Exception Handling
152
153
```kotlin
154
// Throwing and catching exceptions
155
fun riskyOperation() {
156
throw RuntimeException("Something went wrong")
157
}
158
159
try {
160
riskyOperation()
161
} catch (e: RuntimeException) {
162
println("Caught exception: ${e.message}")
163
println("Cause: ${e.cause}")
164
}
165
```
166
167
### Custom Exception Classes
168
169
```kotlin
170
// Create custom exception types
171
class ConfigurationException(
172
message: String,
173
cause: Throwable? = null
174
) : Exception(message, cause)
175
176
class ValidationException(
177
val field: String,
178
message: String
179
) : Exception("Validation failed for field '$field': $message")
180
181
// Usage
182
fun validateUser(user: User) {
183
if (user.name.isBlank()) {
184
throw ValidationException("name", "Name cannot be blank")
185
}
186
if (user.email.contains("@").not()) {
187
throw ValidationException("email", "Invalid email format")
188
}
189
}
190
191
try {
192
validateUser(User("", "invalid-email"))
193
} catch (e: ValidationException) {
194
println("Validation error in ${e.field}: ${e.message}")
195
}
196
```
197
198
### Exception Chaining
199
200
```kotlin
201
// Chain exceptions to preserve error context
202
class DatabaseService {
203
fun saveUser(user: User) {
204
try {
205
// Simulate database operation
206
performDatabaseOperation(user)
207
} catch (e: Exception) {
208
throw RuntimeException("Failed to save user: ${user.name}", e)
209
}
210
}
211
212
private fun performDatabaseOperation(user: User) {
213
// Simulate a lower-level exception
214
throw IllegalStateException("Database connection lost")
215
}
216
}
217
218
// Usage
219
val service = DatabaseService()
220
try {
221
service.saveUser(User("Alice", "alice@example.com"))
222
} catch (e: RuntimeException) {
223
println("Top-level error: ${e.message}")
224
println("Root cause: ${e.cause?.message}")
225
226
// Walk the exception chain
227
var current: Throwable? = e
228
var depth = 0
229
while (current != null) {
230
println(" ${" ".repeat(depth)}${current::class.simpleName}: ${current.message}")
231
current = current.cause
232
depth++
233
}
234
}
235
```
236
237
### Resource Management with Exceptions
238
239
```kotlin
240
// Safe resource management with exception handling
241
class ResourceManager : AutoCloseable {
242
private var closed = false
243
244
fun performOperation() {
245
if (closed) {
246
throw IllegalStateException("Resource is closed")
247
}
248
// Simulate operation that might fail
249
if (Random.nextBoolean()) {
250
throw RuntimeException("Operation failed randomly")
251
}
252
println("Operation completed successfully")
253
}
254
255
override fun close() {
256
if (!closed) {
257
closed = true
258
println("Resource closed")
259
}
260
}
261
}
262
263
// Use with try-with-resources pattern
264
fun useResource() {
265
try {
266
ResourceManager().use { resource ->
267
resource.performOperation()
268
resource.performOperation()
269
}
270
} catch (e: Exception) {
271
println("Operation failed: ${e.message}")
272
}
273
}
274
```
275
276
### Error Recovery Patterns
277
278
```kotlin
279
// Retry with exponential backoff
280
suspend fun retryWithBackoff(
281
maxRetries: Int = 3,
282
baseDelayMs: Long = 1000,
283
operation: suspend () -> Unit
284
) {
285
var lastException: Exception? = null
286
287
repeat(maxRetries) { attempt ->
288
try {
289
operation()
290
return // Success
291
} catch (e: Exception) {
292
lastException = e
293
if (attempt < maxRetries - 1) {
294
val delay = baseDelayMs * (1L shl attempt) // Exponential backoff
295
println("Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${e.message}")
296
kotlinx.coroutines.delay(delay)
297
}
298
}
299
}
300
301
// All retries failed
302
throw RuntimeException("Operation failed after $maxRetries attempts", lastException)
303
}
304
305
// Circuit breaker pattern
306
class CircuitBreaker(
307
private val failureThreshold: Int = 5,
308
private val recoveryTimeMs: Long = 60000
309
) {
310
private var failures = 0
311
private var lastFailureTime = 0L
312
private var state = State.CLOSED
313
314
enum class State { CLOSED, OPEN, HALF_OPEN }
315
316
suspend fun <T> execute(operation: suspend () -> T): T {
317
when (state) {
318
State.OPEN -> {
319
if (System.currentTimeMillis() - lastFailureTime > recoveryTimeMs) {
320
state = State.HALF_OPEN
321
} else {
322
throw RuntimeException("Circuit breaker is OPEN")
323
}
324
}
325
State.HALF_OPEN -> {
326
// Allow one test call
327
}
328
State.CLOSED -> {
329
// Normal operation
330
}
331
}
332
333
try {
334
val result = operation()
335
// Success - reset circuit breaker
336
failures = 0
337
state = State.CLOSED
338
return result
339
} catch (e: Exception) {
340
failures++
341
lastFailureTime = System.currentTimeMillis()
342
343
if (failures >= failureThreshold) {
344
state = State.OPEN
345
}
346
throw e
347
}
348
}
349
}
350
```
351
352
### WASI System Call Error Handling
353
354
```kotlin
355
// Handle WASI-specific errors (internal implementation detail)
356
internal fun handleWasiError(errorCode: Int, operation: String) {
357
if (errorCode != 0) {
358
val wasiError = WasiErrorCode.values().getOrNull(errorCode)
359
val message = when (wasiError) {
360
WasiErrorCode.EBADF -> "Bad file descriptor in $operation"
361
WasiErrorCode.EIO -> "I/O error during $operation"
362
WasiErrorCode.ENOSPC -> "No space left on device during $operation"
363
WasiErrorCode.EPIPE -> "Broken pipe during $operation"
364
WasiErrorCode.EFAULT -> "Bad address during $operation"
365
WasiErrorCode.EINVAL -> "Invalid argument in $operation"
366
WasiErrorCode.ENOSYS -> "Function not implemented: $operation"
367
else -> "WASI error $errorCode during $operation"
368
}
369
throw WasiError(wasiError ?: WasiErrorCode.SUCCESS, message)
370
}
371
}
372
373
// Example usage in I/O operations
374
fun safeWriteToConsole(message: String) {
375
try {
376
println(message)
377
} catch (e: WasiError) {
378
when (e.errorCode) {
379
WasiErrorCode.EPIPE -> {
380
// Output stream closed, log to alternative location
381
System.err.println("Console output unavailable: ${e.message}")
382
}
383
WasiErrorCode.EIO -> {
384
// I/O error, retry once
385
try {
386
Thread.sleep(100)
387
println(message)
388
} catch (retryException: Exception) {
389
System.err.println("Failed to write after retry: ${retryException.message}")
390
}
391
}
392
else -> {
393
System.err.println("Unexpected WASI error: ${e.message}")
394
}
395
}
396
}
397
}
398
```
399
400
## Implementation Details
401
402
### Limited Stack Trace Support
403
404
Due to WebAssembly constraints, stack traces in WASI have limitations:
405
406
- **No Native Stack Walking**: WebAssembly doesn't support native stack trace generation
407
- **Minimal Debug Information**: Stack traces may contain limited function names
408
- **Platform Dependent**: Stack trace quality depends on the WASI runtime implementation
409
410
### Memory-Efficient Exception Handling
411
412
```kotlin
413
// Exceptions are designed for minimal memory overhead
414
val exception = RuntimeException("Error message")
415
// Only stores message and cause references, no heavyweight stack trace data
416
```
417
418
### Error Code Mapping
419
420
WASI system call errors are comprehensively mapped:
421
422
- **82 Error Codes**: All WASI preview1 error codes are supported
423
- **Appropriate Exceptions**: WASI errors are mapped to suitable Kotlin exception types
424
- **Contextual Messages**: Error messages include operation context for better debugging
425
426
## Performance Considerations
427
428
### Exception Creation Cost
429
430
```kotlin
431
// Exceptions are lightweight in WASI
432
val quickException = RuntimeException("Fast creation")
433
434
// Avoid creating exceptions in hot paths when possible
435
fun validateInput(value: String): Boolean {
436
// Prefer returning boolean over throwing exception for validation
437
return value.isNotBlank()
438
}
439
440
// Use exceptions for exceptional cases
441
fun processInput(value: String) {
442
if (!validateInput(value)) {
443
throw IllegalArgumentException("Input cannot be blank")
444
}
445
// Process valid input
446
}
447
```
448
449
### Exception Handling Overhead
450
451
```kotlin
452
// Exception handling has minimal overhead in WASI
453
try {
454
riskyOperation()
455
} catch (e: Exception) {
456
// Minimal catch overhead
457
handleError(e)
458
}
459
460
// However, avoid exceptions for control flow
461
// Bad:
462
fun findUser(id: String): User {
463
try {
464
return users[id] ?: throw NotFoundException()
465
} catch (e: NotFoundException) {
466
return createDefaultUser()
467
}
468
}
469
470
// Good:
471
fun findUser(id: String): User {
472
return users[id] ?: createDefaultUser()
473
}
474
```
475
476
## Best Practices
477
478
### Exception Design
479
480
```kotlin
481
// DO: Create specific exception types for different error conditions
482
class UserNotFoundException(userId: String) : Exception("User not found: $userId")
483
class InvalidCredentialsException : Exception("Invalid username or password")
484
485
// DO: Include relevant context in exception messages
486
class ProcessingException(
487
val stage: String,
488
val itemId: String,
489
cause: Throwable
490
) : Exception("Processing failed at stage '$stage' for item '$itemId'", cause)
491
492
// DON'T: Use generic exceptions for specific problems
493
// throw Exception("Something went wrong") // Too generic
494
```
495
496
### Error Recovery
497
498
```kotlin
499
// DO: Handle exceptions at appropriate levels
500
class UserService {
501
fun createUser(userData: UserData): User {
502
try {
503
validateUserData(userData)
504
return saveUser(userData)
505
} catch (e: ValidationException) {
506
// Handle validation errors specifically
507
throw UserCreationException("Invalid user data", e)
508
} catch (e: DatabaseException) {
509
// Handle database errors specifically
510
throw UserCreationException("Database error during user creation", e)
511
}
512
}
513
}
514
515
// DO: Use finally blocks for cleanup
516
fun processFile(filename: String) {
517
var resource: FileResource? = null
518
try {
519
resource = openFile(filename)
520
processResource(resource)
521
} catch (e: IOException) {
522
logger.error("File processing failed", e)
523
throw ProcessingException("Failed to process file: $filename", e)
524
} finally {
525
resource?.close()
526
}
527
}
528
```
529
530
### Logging and Monitoring
531
532
```kotlin
533
// Good exception logging practices
534
class ErrorHandler {
535
fun handleError(operation: String, error: Throwable) {
536
// Log with appropriate level
537
when (error) {
538
is ValidationException -> {
539
logger.warn("Validation failed in $operation: ${error.message}")
540
}
541
is SecurityException -> {
542
logger.error("Security violation in $operation", error)
543
}
544
else -> {
545
logger.error("Unexpected error in $operation", error)
546
}
547
}
548
549
// Include relevant context
550
logger.error("Error context - Operation: $operation, Time: ${Instant.now()}")
551
552
// Log exception chain
553
var cause = error.cause
554
while (cause != null) {
555
logger.error("Caused by: ${cause::class.simpleName}: ${cause.message}")
556
cause = cause.cause
557
}
558
}
559
}
560
```