puddle
Types
Errors that can occur when applying a function to a pooled resource.
NoResourcesAvailable: Pool exhausted and no lazy capacity (non-blocking checkout).CheckoutTimeout: Timeout while waiting for a resource (blocking checkout).PoolShuttingDown: Pool is shutting down, no new checkouts allowed.
pub type ApplyError {
NoResourcesAvailable
CheckoutTimeout
PoolShuttingDown
}
Constructors
-
NoResourcesAvailable -
CheckoutTimeout -
PoolShuttingDown
Opaque builder type for configuring and creating a resource pool.
Use puddle.new/1 to create a builder, then chain configuration functions
before calling puddle.start/2 or puddle.supervised/2.
pub opaque type Builder(resource_type, result_type)
Strategy for selecting which idle resource to check out.
FIFO(default): Oldest idle resource first. Provides fair ordering.LIFO: Most recently returned resource first. Better cache locality.
pub type CheckoutStrategy {
FIFO
LIFO
}
Constructors
-
FIFO -
LIFO
Strategy for when resources are created in the pool.
Eager(default): Create all resources at pool startup.Lazy: Create resources on first demand, up to the poolsize.
pub type CreationStrategy {
Lazy
Eager
}
Constructors
-
Lazy -
Eager
Internal message type for the pool manager actor.
This type is opaque - use the public API functions instead of sending these messages directly.
pub opaque type ManagerMessage(resource_type, result_type)
The result of a resource usage function, indicating whether to keep or discard the resource after use.
Keep(value): Return the resource to the pool with the result value.Discard(value): Destroy the resource and create a replacement, returning the value.
pub type Next(result_type) {
Keep(result_type)
Discard(result_type)
}
Constructors
-
Keep(result_type) -
Discard(result_type)
Current state of the resource pool.
Ready: Idle resources are available for immediate checkout.Full: All resources are busy, no waiters queued.Overloaded: All resources are busy, requests are queued.
pub type PoolState {
Ready
Full
Overloaded
}
Constructors
-
Ready -
Full -
Overloaded
Status snapshot of the resource pool.
Contains the current state, configured size, and counts of available, busy, and waiting resources/requests.
pub type PoolStatus {
PoolStatus(
state: PoolState,
size: Int,
available: Int,
busy: Int,
waiting: Int,
)
}
Constructors
-
PoolStatus( state: PoolState, size: Int, available: Int, busy: Int, waiting: Int, )
Internal message type for resource worker actors.
This type is opaque - use the public API functions instead.
pub opaque type ResourceMessage(resource_type, result_type)
Values
pub fn apply(
manager: process.Subject(
ManagerMessage(resource_type, result_type),
),
fun: fn(resource_type) -> Next(result_type),
timeout: Int,
rest: fn(Result(result_type, ApplyError)) -> Result(
a,
ApplyError,
),
) -> Result(a, ApplyError)
Apply a function to a pooled resource (non-blocking checkout).
Checks out a resource, applies fun to it, and returns the result.
If no resource is available and the pool has no lazy capacity,
returns Error(NoResourcesAvailable) immediately.
The fun function receives the resource and must return a Next(result_type):
puddle.keep(value)- return resource to pool, continue withvaluepuddle.discard(value)- destroy resource, create replacement, continue withvalue
The timeout is the maximum time (in milliseconds) to wait for:
- Resource checkout (if pool not exhausted)
- Function execution and result
Uses the rest callback to handle the final result or error.
let result = {
use r <- puddle.apply(manager, fn(conn) {
case db.query(conn, "SELECT 1") {
Ok(rows) -> puddle.keep(rows)
Error(_) -> puddle.discard([])
}
}, 1000)
r
}
pub fn apply_blocking(
manager: process.Subject(
ManagerMessage(resource_type, result_type),
),
fun: fn(resource_type) -> Next(result_type),
timeout: Int,
rest: fn(Result(result_type, ApplyError)) -> Result(
a,
ApplyError,
),
) -> Result(a, ApplyError)
Apply a function to a pooled resource (blocking checkout).
Similar to apply/4, but if all resources are busy, the request
is queued until a resource becomes available or the timeout expires.
If a resource becomes available within timeout milliseconds, the
function is applied and the result returned. Otherwise, returns
Error(CheckoutTimeout).
The timeout applies to the total time waiting for a resource
plus function execution.
let result = {
use r <- puddle.apply_blocking(manager, fn(n) { puddle.keep(n) }, 5000)
r
}
pub fn checkout_strategy(
builder: Builder(resource_type, result_type),
strategy: CheckoutStrategy,
) -> Builder(resource_type, result_type)
Set the checkout strategy for selecting idle resources.
Default: FIFO
FIFO: Oldest idle resource first (fair ordering)LIFO: Most recently returned resource first (better cache locality)
pub fn creation_strategy(
builder: Builder(resource_type, result_type),
strategy: CreationStrategy,
) -> Builder(resource_type, result_type)
Set the resource creation strategy.
Default: Eager
Eager: Create all resources at pool startupLazy: Create resources on first demand, up tosize
pub fn discard(value: result_type) -> Next(result_type)
Signal to discard the resource and create a replacement.
The resource will be shut down via the on_shutdown callback and a new
resource will be created (up to the pool size limit).
Returns Next(result_type) wrapping the value to pass to the continuation.
pub fn keep(value: result_type) -> Next(result_type)
Signal to keep the resource in the pool after use.
Returns Next(result_type) wrapping the value to pass to the continuation.
pub fn name(
builder: Builder(resource_type, result_type),
pool_name: process.Name(
ManagerMessage(resource_type, result_type),
),
) -> Builder(resource_type, result_type)
Register the pool under a globally accessible name.
The pool can then be accessed from anywhere using
process.named_subject(name) without passing the manager reference.
import gleam/erlang/process
let pool_name = process.new_name("my_db_pool")
let assert Ok(manager) =
puddle.new(create_connection)
|> puddle.size(10)
|> puddle.name(pool_name)
|> puddle.start(5000)
// Later, from anywhere:
let manager = process.named_subject(pool_name)
pub fn new(
create_resource: fn() -> Result(resource_type, Nil),
) -> Builder(resource_type, result_type)
Create a new pool builder with a resource creation function.
The create_resource function is called to create new resources.
It should return Ok(resource) on success or Error(Nil) on failure.
Default configuration:
- Size: 10
- Checkout strategy: FIFO
- Creation strategy: Eager
- Shutdown callback: no-op
- Name: None
pub fn on_shutdown(
builder: Builder(resource_type, result_type),
callback: fn(resource_type) -> Nil,
) -> Builder(resource_type, result_type)
Set a callback to run when each resource is shut down.
The callback is called for every resource in the pool when:
- The pool is shut down via
puddle.shutdown/1 - A resource is discarded via
puddle.discard/1 - A worker process crashes and is replaced
Use this to clean up resources (e.g., close database connections).
Default: no-op
pub fn shutdown(
manager: process.Subject(
ManagerMessage(resource_type, result_type),
),
) -> Nil
Gracefully shut down the resource pool.
Sends a shutdown signal to the pool manager. The manager will:
- Stop accepting new checkouts
- Wait for currently checked-out resources to be returned
- Call the
on_shutdowncallback for each resource - Stop the manager actor
This function returns immediately; shutdown happens asynchronously.
Use puddle.status/2 to monitor shutdown progress if needed.
puddle.shutdown(manager)
pub fn size(
builder: Builder(resource_type, result_type),
size: Int,
) -> Builder(resource_type, result_type)
Set the maximum number of resources in the pool.
Default: 10
When using Lazy creation strategy, resources are created up to this limit
on demand. When using Eager strategy, this many resources are created at startup.
pub fn start(
builder: Builder(resource_type, result_type),
timeout: Int,
) -> Result(
process.Subject(ManagerMessage(resource_type, result_type)),
actor.StartError,
)
Start the resource pool and return a manager subject.
The pool is started as a supervised actor. The timeout parameter
is the maximum time (in milliseconds) to wait for the pool to start
and for initial resource creation (if using Eager strategy).
Returns Ok(manager_subject) on success, or Error(StartError) if
the actor fails to start or resource creation fails.
let assert Ok(manager) =
puddle.new(create_connection)
|> puddle.size(10)
|> puddle.start(5000)
pub fn status(
manager: process.Subject(
ManagerMessage(resource_type, result_type),
),
timeout: Int,
) -> PoolStatus
Get the current status of the resource pool.
Returns a PoolStatus record containing:
state:Ready,Full, orOverloadedsize: configured pool sizeavailable: number of idle resourcesbusy: number of checked-out resourceswaiting: number of queued blocking requests
The timeout is the maximum time (in milliseconds) to wait for
the status response from the manager.
let status = puddle.status(manager, 1000)
case status.state {
puddle.Ready -> io.debug("Pool ready")
puddle.Full -> io.debug("Pool full")
puddle.Overloaded -> io.debug("Pool overloaded")
}
pub fn supervised(
builder: Builder(resource_type, result_type),
timeout: Int,
) -> supervision.ChildSpecification(
process.Subject(ManagerMessage(resource_type, result_type)),
)
Create a child specification for running the pool under an OTP supervisor.
The pool will be started as a worker under the supervisor. The timeout
parameter is the maximum time (in milliseconds) to wait for the pool
to start and for initial resource creation (if using Eager strategy).
Use with gleam/otp/static_supervisor or gleam/otp/dynamic_supervisor.
import gleam/otp/static_supervisor
let child_spec =
puddle.new(create_resource)
|> puddle.size(5)
|> puddle.supervised(5000)
let assert Ok(_supervisor) =
static_supervisor.new(static_supervisor.OneForOne)
|> static_supervisor.add(child_spec)
|> static_supervisor.start