or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

byte-order-operations.mdchannel-factories.mdchannel-interfaces.mdchannel-operations.mdcharacter-encoding.mdindex.mdobject-pooling.md
tile.json

object-pooling.mddocs/

Object Pooling

Memory-efficient object pooling system for reusing expensive objects like byte arrays, reducing garbage collection pressure in high-performance scenarios.

Capabilities

ObjectPool Interface

Core pooling abstraction for managing reusable objects.

/**
 * Pool for reusing objects of type T to reduce allocation overhead
 */
interface ObjectPool<T : Any> : AutoCloseable {
    /** Maximum number of objects this pool can hold */
    val capacity: Int
    
    /** Borrow an object from the pool */
    fun borrow(): T
    
    /** Return an object to the pool for reuse */
    fun recycle(instance: T)
    
    /** Dispose of the pool and all its objects */
    fun dispose()
    
    /** AutoCloseable implementation that calls dispose() */
    override fun close()
}

/** Use an object from the pool within a block and automatically recycle it */
inline fun <T : Any, R> ObjectPool<T>.useInstance(block: (T) -> R): R

Usage Examples:

import io.ktor.utils.io.pool.*

fun basicPoolingExample() {
    // Create a custom pool implementation
    val stringPool = object : ObjectPool<StringBuilder> {
        private val pool = mutableListOf<StringBuilder>()
        override val capacity = 10
        
        override fun borrow(): StringBuilder {
            return if (pool.isNotEmpty()) {
                pool.removeAt(pool.size - 1).apply { clear() }
            } else {
                StringBuilder()
            }
        }
        
        override fun recycle(instance: StringBuilder) {
            if (pool.size < capacity) {
                pool.add(instance)
            }
        }
        
        override fun dispose() {
            pool.clear()
        }
    }
    
    // Use the pool
    val result = stringPool.useInstance { builder ->
        builder.append("Hello")
        builder.append(" World")
        builder.toString()
    }
    
    println(result) // "Hello World"
    // StringBuilder is automatically recycled
}

Pool Implementations

Built-in pool implementations for different use cases.

/** Pool implementation with zero capacity - always creates new objects */
abstract class NoPoolImpl<T : Any> : ObjectPool<T> {
    override val capacity: Int = 0
    override fun recycle(instance: T) { /* no-op */ }
    override fun dispose() { /* no-op */ }
}

/** Pool implementation that reuses a single instance */
abstract class SingleInstancePool<T : Any> : ObjectPool<T> {
    override val capacity: Int = 1
}

/** Platform-specific default pool implementation */
expect abstract class DefaultPool<T : Any>(capacity: Int) : ObjectPool<T>

Usage Examples:

import io.ktor.utils.io.pool.*

// No-pool implementation for objects that shouldn't be pooled
class StringNoPool : NoPoolImpl<String>() {
    override fun borrow(): String = ""
}

// Single instance pool for expensive objects
class ExpensiveObjectPool(private val factory: () -> ExpensiveObject) : SingleInstancePool<ExpensiveObject>() {
    private var instance: ExpensiveObject? = null
    
    override fun borrow(): ExpensiveObject {
        return instance ?: factory().also { instance = it }
    }
    
    override fun recycle(instance: ExpensiveObject) {
        this.instance = instance
        instance.reset() // Reset object state
    }
    
    override fun dispose() {
        instance?.cleanup()
        instance = null
    }
}

// Platform-specific default pool
class BufferPool : DefaultPool<ByteArray>(capacity = 100) {
    override fun produceInstance(): ByteArray = ByteArray(8192)
    override fun clearInstance(instance: ByteArray): ByteArray = instance.apply { fill(0) }
}

ByteArrayPool

Pre-built pool for commonly used byte arrays.

/** Global pool for ByteArray objects */
val ByteArrayPool: ObjectPool<ByteArray>

Usage Examples:

import io.ktor.utils.io.pool.*

suspend fun byteArrayPoolExample() {
    // Use the global byte array pool
    val result = ByteArrayPool.useInstance { buffer ->
        // Use the buffer for operations
        val data = "Hello, Pool!".toByteArray()
        data.copyInto(buffer, 0, 0, data.size)
        
        // Process buffer content
        processBuffer(buffer, data.size)
    }
    // Buffer is automatically recycled
    
    // Manual borrow and recycle
    val buffer = ByteArrayPool.borrow()
    try {
        // Use buffer
        fillBuffer(buffer)
        processBuffer(buffer, buffer.size)
    } finally {
        ByteArrayPool.recycle(buffer)
    }
}

fun processBuffer(buffer: ByteArray, length: Int) {
    // Process buffer content
    println("Processing ${length} bytes")
}

fun fillBuffer(buffer: ByteArray) {
    // Fill buffer with data
    for (i in buffer.indices) {
        buffer[i] = (i % 256).toByte()
    }
}

Pool Integration with Channels

Using pools with byte channel operations for memory efficiency.

// Pool integration is implicit in channel operations
// Channels may use pools internally for buffer management

Usage Examples:

import io.ktor.utils.io.*
import io.ktor.utils.io.pool.*

suspend fun poolChannelIntegration() {
    // Custom channel operations using pooled buffers
    val channel = ByteChannel()
    
    // Write using pooled buffer
    ByteArrayPool.useInstance { buffer ->
        val data = "Pooled data transfer".toByteArray()
        data.copyInto(buffer, 0, 0, data.size)
        channel.writeByteArray(buffer.sliceArray(0 until data.size))
    }
    
    // Read using pooled buffer
    val content = ByteArrayPool.useInstance { buffer ->
        val bytesRead = channel.readAvailable(buffer, 0, buffer.size)
        if (bytesRead > 0) {
            buffer.sliceArray(0 until bytesRead)
        } else {
            byteArrayOf()
        }
    }
    
    println("Read: ${String(content)}")
}

Custom Pool Implementation

Creating custom pools for specific object types.

// Custom pool implementation pattern
abstract class CustomPool<T : Any>(capacity: Int) : DefaultPool<T>(capacity) {
    abstract fun produceInstance(): T
    open fun clearInstance(instance: T): T = instance
    open fun validateInstance(instance: T): Boolean = true
}

Usage Examples:

import io.ktor.utils.io.pool.*

// Custom pool for StringBuilder objects
class StringBuilderPool(capacity: Int = 50) : DefaultPool<StringBuilder>(capacity) {
    override fun produceInstance(): StringBuilder = StringBuilder(1024)
    
    override fun clearInstance(instance: StringBuilder): StringBuilder {
        instance.clear()
        return instance
    }
    
    override fun validateInstance(instance: StringBuilder): Boolean {
        // Ensure capacity hasn't grown too large
        return instance.capacity() <= 10240
    }
}

// Custom pool for network buffers
class NetworkBufferPool(
    private val bufferSize: Int = 8192,
    capacity: Int = 20
) : DefaultPool<ByteArray>(capacity) {
    
    override fun produceInstance(): ByteArray = ByteArray(bufferSize)
    
    override fun clearInstance(instance: ByteArray): ByteArray {
        instance.fill(0) // Clear sensitive data
        return instance
    }
    
    override fun validateInstance(instance: ByteArray): Boolean {
        return instance.size == bufferSize
    }
}

// Usage
suspend fun customPoolExample() {
    val stringPool = StringBuilderPool()
    val bufferPool = NetworkBufferPool(bufferSize = 4096)
    
    // Use string builder pool
    val text = stringPool.useInstance { builder ->
        builder.append("Generated at: ")
        builder.append(System.currentTimeMillis())
        builder.toString()
    }
    
    // Use buffer pool
    val processedData = bufferPool.useInstance { buffer ->
        // Simulate network operation
        val data = "Network data".toByteArray()
        data.copyInto(buffer, 0, 0, data.size)
        
        // Process buffer
        processNetworkData(buffer, data.size)
    }
    
    // Clean up pools when done
    stringPool.dispose()
    bufferPool.dispose()
}

fun processNetworkData(buffer: ByteArray, length: Int): ByteArray {
    // Process network data and return result
    return buffer.sliceArray(0 until length)
}

Pool Management Patterns

Pool Lifecycle

// Create pool
val pool = CustomPool<MyObject>(capacity = 50)

// Use throughout application lifecycle
repeat(1000) {
    pool.useInstance { obj ->
        // Use object
    }
}

// Dispose when shutting down
pool.dispose()

Safe Usage Patterns

// Always use try-finally or useInstance
val pool = ByteArrayPool

// Preferred: automatic recycling
pool.useInstance { buffer ->
    // Use buffer safely
    processData(buffer)
}

// Manual: ensure recycling
val buffer = pool.borrow()
try {
    processData(buffer)
} finally {
    pool.recycle(buffer)
}

Pool Configuration

// Configure pools based on use case
val smallBufferPool = NetworkBufferPool(bufferSize = 1024, capacity = 100)
val largeBufferPool = NetworkBufferPool(bufferSize = 64 * 1024, capacity = 10)

// High-frequency, small objects
val tokenPool = StringBuilderPool(capacity = 200)

// Low-frequency, expensive objects  
val connectionPool = CustomPool<DatabaseConnection>(capacity = 5)