moonbitstack/moondb/apool does not have a README file

    AsyncPool

    pub struct AsyncPool[D] {
    // private fields
    }

    A connection pool for an [@moondb.AsyncDriver] — the counterpart of [@moondb/pool.Pool] for a backend reached over the network.

    It exists because a synchronous pool cannot do the one thing an exhausted pool is supposed to do: wait. In [@moondb/pool.Pool] nothing can return a connection while a caller is inside acquire, so a full pool fails immediately and acquire_timeout_ms only reaches the error message. Here the caller is a task in an event loop: other tasks keep running while it waits, so a full pool parks the caller on a permit and wakes it when a connection comes home. That is what database/sql and SQLAlchemy's QueuePool give their users, and it is what makes overflow and a real timeout meaningful.

    The division of labour mirrors the sync pool: [@moonpool] keeps the accounting — live, idle, lent, each connection's birth time — and this type adds what only a driver can do (opening, probing, resetting, closing) plus the permit that makes an exhausted acquire wait.

    let pool : @apool.AsyncPool[@postgres.PgConn] = @apool.AsyncPool::new(
    () => @postgres.PgConn::connect("db", 5432, "app", "secret", "app"),
    max_size=8,
    overflow=2,
    pre_ping=true,
    acquire_timeout_ms=3000L,
    reset=conn => conn.rollback() catch { _ => () },
    )

    let rows = pool.with_conn(conn => conn.query("SELECT 1", []))

    AsyncPool::acquire

    Take a connection, waiting for one if the pool is at capacity.

    The wait is real: the calling task parks on the permit and other tasks in the loop keep running, so a connection released by another task wakes this one. With acquire_timeout_ms set, a wait longer than the budget raises QueryError.

    An idle connection past max_lifetime_ms, or one that fails the [AsyncDriver::ping] probe under pre_ping, is closed and skipped rather than handed back — in both cases the slot is refilled, so a stale connection does not shrink the pool. Raises [Closed] if the pool has been closed, ConnectError if the ward holds back or refuses a new connection, and whatever make raised if opening a fresh connection fails.

    Return the connection with [release] — or use [with_conn], which does so on every path.

    AsyncPool::acquire_timeout_ms

    fn[D] AsyncPool::acquire_timeout_ms(self : AsyncPool[D]) -> Int64

    How long an acquire may wait for a permit in milliseconds (0 = no limit).

    AsyncPool::capacity

    fn[D] AsyncPool::capacity(self : AsyncPool[D]) -> Int

    The hard ceiling on concurrent checkouts: max_size + overflow.

    AsyncPool::close_all

    async fn[D :
    AsyncDriver
    ] AsyncPool::close_all(self : AsyncPool[D]) -> Unit noraise

    Close every idle connection and mark the pool closed. Connections still lent out are not cut off — each closes as it is [release]d back — so this is the graceful half of a shutdown. After this, acquire raises [Closed]. Idempotent.

    AsyncPool::idle_count

    fn[D] AsyncPool::idle_count(self : AsyncPool[D]) -> Int

    How many connections are idle and reusable right now.

    AsyncPool::in_use_count

    fn[D] AsyncPool::in_use_count(self : AsyncPool[D]) -> Int

    How many connections are lent out right now.

    AsyncPool::is_closed

    fn[D] AsyncPool::is_closed(self : AsyncPool[D]) -> Bool

    Whether [close_all] has been called.

    AsyncPool::max_lifetime_ms

    fn[D] AsyncPool::max_lifetime_ms(self : AsyncPool[D]) -> Int64

    The configured connection lifetime in milliseconds (0 = unlimited).

    AsyncPool::max_size

    fn[D] AsyncPool::max_size(self : AsyncPool[D]) -> Int

    The steady-state ceiling on open connections.

    AsyncPool::new

    fn[D] AsyncPool::new(make : async () -> D raise
    DbError
    , max_size? : Int, overflow? : Int, max_lifetime_ms? : Int64, pre_ping? : Bool, acquire_timeout_ms? : Int64, clock? : () -> Int64, ward? :
    Ward
    , reset? : async (D) -> Unit raise
    DbError
    , rand? : () -> Double, cpu? : () -> Int) -> AsyncPool[D] raise
    DbError

    Build a pool whose connections come from make, which runs inside the event loop and may await the handshake.

    • max_size caps the steady-state number of open connections (default 10) and must be positive.
    • overflow is how many connections may be opened above max_size while every steady-state connection is lent; a connection that came from that allowance is closed on return rather than idled, so the pool settles back to max_size.
    • max_lifetime_ms retires a connection older than this on acquire (0, the default, means no age limit). Enforcing it requires a real clock.
    • pre_ping probes an idle connection with [AsyncDriver::ping] before handing it out, closing it and trying the next if the backend has dropped it.
    • acquire_timeout_ms is how long a caller may wait for a permit before failing (0, the default, means wait as long as it takes — the pool never gives up on its own).
    • reset runs on return before the connection becomes reusable: roll back a leftover transaction, clear session state. A reset that raises closes the connection instead of pooling it.
    • clock returns monotonic milliseconds and defaults to a constant 0, which disables age-based retirement; rand/cpu feed the ward's jitter and shedder.

    AsyncPool::open_count

    fn[D] AsyncPool::open_count(self : AsyncPool[D]) -> Int

    How many connections the pool has open: lent plus idle.

    AsyncPool::overflow

    fn[D] AsyncPool::overflow(self : AsyncPool[D]) -> Int

    How many connections may be opened above max_size while the pool is busy.

    AsyncPool::pre_ping

    fn[D] AsyncPool::pre_ping(self : AsyncPool[D]) -> Bool

    Whether the pre-ping probe is enabled.

    AsyncPool::release

    async fn[D :
    AsyncDriver
    ] AsyncPool::release(self : AsyncPool[D], conn : D) -> Unit noraise

    Return a connection so a later [acquire] can reuse it, and wake one waiter.

    The reset hook runs first; under overflow, a connection that pushed the open set above max_size is closed instead of idled so the pool settles back to its steady-state size. A connection returned to a closed pool is closed immediately, so no handle outlives [close_all]. Every path releases the permit exactly once.

    AsyncPool::try_acquire

    Take a connection only if the pool is not at capacity, returning None instead of waiting. This is the load-shedding counterpart of [acquire]: a caller that would rather refuse work than queue can pick its own answer. Health and lifetime eviction apply exactly as in acquire, and a genuine failure while opening a fresh connection still propagates.

    AsyncPool::with_conn

    Run f with a pooled connection and return that connection afterwards, whether f returns or raises. This is the leak-proof way to use the pool.

    Source Files