CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlin--kotlin-stdlib-wasm-wasi

Kotlin Standard Library implementation for WebAssembly System Interface (WASI) platform providing essential I/O, time, random, UUID, and reflection capabilities.

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

Overview

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.

API Reference

Throwable Base Class

/**
 * The base class for all errors and exceptions in WASI environment.
 * Provides limited stack trace support due to WebAssembly constraints.
 */
actual class Throwable {
    /**
     * Creates a throwable with no message and no cause.
     */
    actual constructor()
    
    /**
     * Creates a throwable with the specified message.
     * @param message the detail message
     */
    actual constructor(message: String?)
    
    /**
     * Creates a throwable with the specified cause.
     * @param cause the cause of this throwable
     */
    actual constructor(cause: Throwable?)
    
    /**
     * Creates a throwable with the specified message and cause.
     * @param message the detail message
     * @param cause the cause of this throwable
     */
    actual constructor(message: String?, cause: Throwable?)
    
    /**
     * The detail message of this throwable.
     */
    actual val message: String?
    
    /**
     * The cause of this throwable, or null if the cause is unknown.
     */
    actual val cause: Throwable?
}

WASI Error Handling

/**
 * Internal WASI error codes from preview1 specification.
 * Used for mapping WASI system call errors to Kotlin exceptions.
 */
internal enum class WasiErrorCode {
    SUCCESS,        // No error occurred
    E2BIG,         // Argument list too long
    EACCES,        // Permission denied
    EADDRINUSE,    // Address already in use
    EADDRNOTAVAIL, // Address not available
    EAFNOSUPPORT,  // Address family not supported
    EAGAIN,        // Resource temporarily unavailable
    EALREADY,      // Connection already in progress
    EBADF,         // Bad file descriptor
    EBADMSG,       // Bad message
    EBUSY,         // Device or resource busy
    ECANCELED,     // Operation canceled
    ECHILD,        // No child processes
    ECONNABORTED,  // Connection aborted
    ECONNREFUSED,  // Connection refused
    ECONNRESET,    // Connection reset
    EDEADLK,       // Resource deadlock avoided
    EDESTADDRREQ,  // Destination address required
    EDOM,          // Mathematics argument out of domain
    EDQUOT,        // Disk quota exceeded
    EEXIST,        // File exists
    EFAULT,        // Bad address
    EFBIG,         // File too large
    EHOSTUNREACH,  // Host is unreachable
    EIDRM,         // Identifier removed
    EILSEQ,        // Invalid or incomplete multibyte sequence
    EINPROGRESS,   // Operation in progress
    EINTR,         // Interrupted system call
    EINVAL,        // Invalid argument
    EIO,           // Input/output error
    EISCONN,       // Socket is connected
    EISDIR,        // Is a directory
    ELOOP,         // Symbolic link loop
    EMFILE,        // Too many open files
    EMLINK,        // Too many links
    EMSGSIZE,      // Message too large
    EMULTIHOP,     // Multihop attempted
    ENAMETOOLONG,  // File name too long
    ENETDOWN,      // Network is down
    ENETRESET,     // Network dropped connection on reset
    ENETUNREACH,   // Network is unreachable
    ENFILE,        // Too many open files in system
    ENOBUFS,       // No buffer space available
    ENODEV,        // No such device
    ENOENT,        // No such file or directory
    ENOEXEC,       // Executable file format error
    ENOLCK,        // No locks available
    ENOLINK,       // Link has been severed
    ENOMEM,        // Cannot allocate memory
    ENOMSG,        // No message of desired type
    ENOPROTOOPT,   // Protocol not available
    ENOSPC,        // No space left on device
    ENOSYS,        // Function not implemented
    ENOTCONN,      // Socket is not connected
    ENOTDIR,       // Not a directory
    ENOTEMPTY,     // Directory not empty
    ENOTRECOVERABLE, // State not recoverable
    ENOTSOCK,      // Not a socket
    ENOTSUP,       // Operation not supported
    ENOTTY,        // Inappropriate I/O control operation
    ENXIO,         // No such device or address
    EOVERFLOW,     // Value too large for data type
    EOWNERDEAD,    // Previous owner died
    EPERM,         // Operation not permitted
    EPIPE,         // Broken pipe
    EPROTO,        // Protocol error
    EPROTONOSUPPORT, // Protocol not supported
    EPROTOTYPE,    // Protocol wrong type for socket
    ERANGE,        // Result too large
    EROFS,         // Read-only file system
    ESPIPE,        // Invalid seek
    ESRCH,         // No such process
    ESTALE,        // Stale file handle
    ETIMEDOUT,     // Connection timed out
    ETXTBSY,       // Text file busy
    EXDEV,         // Invalid cross-device link
    ENOTCAPABLE    // Capability insufficient
}

/**
 * Internal exception class for WASI-specific errors.
 * Maps WASI error codes to Kotlin exceptions with appropriate messages.
 */
internal class WasiError(
    val errorCode: WasiErrorCode,
    message: String = errorCode.name
) : Exception(message)

Usage Examples

Basic Exception Handling

// Throwing and catching exceptions
fun riskyOperation() {
    throw RuntimeException("Something went wrong")
}

try {
    riskyOperation()
} catch (e: RuntimeException) {
    println("Caught exception: ${e.message}")
    println("Cause: ${e.cause}")
}

Custom Exception Classes

// Create custom exception types
class ConfigurationException(
    message: String,
    cause: Throwable? = null
) : Exception(message, cause)

class ValidationException(
    val field: String,
    message: String
) : Exception("Validation failed for field '$field': $message")

// Usage
fun validateUser(user: User) {
    if (user.name.isBlank()) {
        throw ValidationException("name", "Name cannot be blank")
    }
    if (user.email.contains("@").not()) {
        throw ValidationException("email", "Invalid email format")
    }
}

try {
    validateUser(User("", "invalid-email"))
} catch (e: ValidationException) {
    println("Validation error in ${e.field}: ${e.message}")
}

Exception Chaining

// Chain exceptions to preserve error context
class DatabaseService {
    fun saveUser(user: User) {
        try {
            // Simulate database operation
            performDatabaseOperation(user)
        } catch (e: Exception) {
            throw RuntimeException("Failed to save user: ${user.name}", e)
        }
    }
    
    private fun performDatabaseOperation(user: User) {
        // Simulate a lower-level exception
        throw IllegalStateException("Database connection lost")
    }
}

// Usage
val service = DatabaseService()
try {
    service.saveUser(User("Alice", "alice@example.com"))
} catch (e: RuntimeException) {
    println("Top-level error: ${e.message}")
    println("Root cause: ${e.cause?.message}")
    
    // Walk the exception chain
    var current: Throwable? = e
    var depth = 0
    while (current != null) {
        println("  ${"  ".repeat(depth)}${current::class.simpleName}: ${current.message}")
        current = current.cause
        depth++
    }
}

Resource Management with Exceptions

// Safe resource management with exception handling
class ResourceManager : AutoCloseable {
    private var closed = false
    
    fun performOperation() {
        if (closed) {
            throw IllegalStateException("Resource is closed")
        }
        // Simulate operation that might fail
        if (Random.nextBoolean()) {
            throw RuntimeException("Operation failed randomly")
        }
        println("Operation completed successfully")
    }
    
    override fun close() {
        if (!closed) {
            closed = true
            println("Resource closed")
        }
    }
}

// Use with try-with-resources pattern
fun useResource() {
    try {
        ResourceManager().use { resource ->
            resource.performOperation()
            resource.performOperation()
        }
    } catch (e: Exception) {
        println("Operation failed: ${e.message}")
    }
}

Error Recovery Patterns

// Retry with exponential backoff
suspend fun retryWithBackoff(
    maxRetries: Int = 3,
    baseDelayMs: Long = 1000,
    operation: suspend () -> Unit
) {
    var lastException: Exception? = null
    
    repeat(maxRetries) { attempt ->
        try {
            operation()
            return // Success
        } catch (e: Exception) {
            lastException = e
            if (attempt < maxRetries - 1) {
                val delay = baseDelayMs * (1L shl attempt) // Exponential backoff
                println("Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${e.message}")
                kotlinx.coroutines.delay(delay)
            }
        }
    }
    
    // All retries failed
    throw RuntimeException("Operation failed after $maxRetries attempts", lastException)
}

// Circuit breaker pattern
class CircuitBreaker(
    private val failureThreshold: Int = 5,
    private val recoveryTimeMs: 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(operation: suspend () -> T): T {
        when (state) {
            State.OPEN -> {
                if (System.currentTimeMillis() - lastFailureTime > recoveryTimeMs) {
                    state = State.HALF_OPEN
                } else {
                    throw RuntimeException("Circuit breaker is OPEN")
                }
            }
            State.HALF_OPEN -> {
                // Allow one test call
            }
            State.CLOSED -> {
                // Normal operation
            }
        }
        
        try {
            val result = operation()
            // Success - reset circuit breaker
            failures = 0
            state = State.CLOSED
            return result
        } catch (e: Exception) {
            failures++
            lastFailureTime = System.currentTimeMillis()
            
            if (failures >= failureThreshold) {
                state = State.OPEN
            }
            throw e
        }
    }
}

WASI System Call Error Handling

// Handle WASI-specific errors (internal implementation detail)
internal fun handleWasiError(errorCode: Int, operation: String) {
    if (errorCode != 0) {
        val wasiError = WasiErrorCode.values().getOrNull(errorCode)
        val message = when (wasiError) {
            WasiErrorCode.EBADF -> "Bad file descriptor in $operation"
            WasiErrorCode.EIO -> "I/O error during $operation"
            WasiErrorCode.ENOSPC -> "No space left on device during $operation"
            WasiErrorCode.EPIPE -> "Broken pipe during $operation"
            WasiErrorCode.EFAULT -> "Bad address during $operation"
            WasiErrorCode.EINVAL -> "Invalid argument in $operation"
            WasiErrorCode.ENOSYS -> "Function not implemented: $operation"
            else -> "WASI error $errorCode during $operation"
        }
        throw WasiError(wasiError ?: WasiErrorCode.SUCCESS, message)
    }
}

// Example usage in I/O operations
fun safeWriteToConsole(message: String) {
    try {
        println(message)
    } catch (e: WasiError) {
        when (e.errorCode) {
            WasiErrorCode.EPIPE -> {
                // Output stream closed, log to alternative location
                System.err.println("Console output unavailable: ${e.message}")
            }
            WasiErrorCode.EIO -> {
                // I/O error, retry once
                try {
                    Thread.sleep(100)
                    println(message)
                } catch (retryException: Exception) {
                    System.err.println("Failed to write after retry: ${retryException.message}")
                }
            }
            else -> {
                System.err.println("Unexpected WASI error: ${e.message}")
            }
        }
    }
}

Implementation Details

Limited Stack Trace Support

Due to WebAssembly constraints, stack traces in WASI have limitations:

  • No Native Stack Walking: WebAssembly doesn't support native stack trace generation
  • Minimal Debug Information: Stack traces may contain limited function names
  • Platform Dependent: Stack trace quality depends on the WASI runtime implementation

Memory-Efficient Exception Handling

// Exceptions are designed for minimal memory overhead
val exception = RuntimeException("Error message")
// Only stores message and cause references, no heavyweight stack trace data

Error Code Mapping

WASI system call errors are comprehensively mapped:

  • 82 Error Codes: All WASI preview1 error codes are supported
  • Appropriate Exceptions: WASI errors are mapped to suitable Kotlin exception types
  • Contextual Messages: Error messages include operation context for better debugging

Performance Considerations

Exception Creation Cost

// Exceptions are lightweight in WASI
val quickException = RuntimeException("Fast creation")

// Avoid creating exceptions in hot paths when possible
fun validateInput(value: String): Boolean {
    // Prefer returning boolean over throwing exception for validation
    return value.isNotBlank()
}

// Use exceptions for exceptional cases
fun processInput(value: String) {
    if (!validateInput(value)) {
        throw IllegalArgumentException("Input cannot be blank")
    }
    // Process valid input
}

Exception Handling Overhead

// Exception handling has minimal overhead in WASI
try {
    riskyOperation()
} catch (e: Exception) {
    // Minimal catch overhead
    handleError(e)
}

// However, avoid exceptions for control flow
// Bad:
fun findUser(id: String): User {
    try {
        return users[id] ?: throw NotFoundException()
    } catch (e: NotFoundException) {
        return createDefaultUser()
    }
}

// Good:
fun findUser(id: String): User {
    return users[id] ?: createDefaultUser()
}

Best Practices

Exception Design

// DO: Create specific exception types for different error conditions
class UserNotFoundException(userId: String) : Exception("User not found: $userId")
class InvalidCredentialsException : Exception("Invalid username or password")

// DO: Include relevant context in exception messages
class ProcessingException(
    val stage: String,
    val itemId: String,
    cause: Throwable
) : Exception("Processing failed at stage '$stage' for item '$itemId'", cause)

// DON'T: Use generic exceptions for specific problems
// throw Exception("Something went wrong") // Too generic

Error Recovery

// DO: Handle exceptions at appropriate levels
class UserService {
    fun createUser(userData: UserData): User {
        try {
            validateUserData(userData)
            return saveUser(userData)
        } catch (e: ValidationException) {
            // Handle validation errors specifically
            throw UserCreationException("Invalid user data", e)
        } catch (e: DatabaseException) {
            // Handle database errors specifically  
            throw UserCreationException("Database error during user creation", e)
        }
    }
}

// DO: Use finally blocks for cleanup
fun processFile(filename: String) {
    var resource: FileResource? = null
    try {
        resource = openFile(filename)
        processResource(resource)
    } catch (e: IOException) {
        logger.error("File processing failed", e)
        throw ProcessingException("Failed to process file: $filename", e)
    } finally {
        resource?.close()
    }
}

Logging and Monitoring

// Good exception logging practices
class ErrorHandler {
    fun handleError(operation: String, error: Throwable) {
        // Log with appropriate level
        when (error) {
            is ValidationException -> {
                logger.warn("Validation failed in $operation: ${error.message}")
            }
            is SecurityException -> {
                logger.error("Security violation in $operation", error)
            }
            else -> {
                logger.error("Unexpected error in $operation", error)
            }
        }
        
        // Include relevant context
        logger.error("Error context - Operation: $operation, Time: ${Instant.now()}")
        
        // Log exception chain
        var cause = error.cause
        while (cause != null) {
            logger.error("Caused by: ${cause::class.simpleName}: ${cause.message}")
            cause = cause.cause
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-stdlib-wasm-wasi

docs

console-io.md

exceptions.md

index.md

random.md

reflection.md

time.md

uuid.md

tile.json