Skip to content

Add configurable checkout timeout for transactions#83

Open
mgruen wants to merge 2 commits intolpil:mainfrom
mgruen:fix-transaction-checkout-timeout
Open

Add configurable checkout timeout for transactions#83
mgruen wants to merge 2 commits intolpil:mainfrom
mgruen:fix-transaction-checkout-timeout

Conversation

@mgruen
Copy link
Copy Markdown

@mgruen mgruen commented Apr 9, 2026

Summary

  • pog.transaction called pgo:checkout/1 with no timeout, defaulting to pgo_pool's hardcoded 5000ms
  • Add checkout_timeout field to Config (default: 5000ms, backwards compatible)
  • Add checkout_timeout/2 setter so users can increase it
  • Pass the timeout through to pgo:checkout/2 in the transaction path

Problem

Serverless databases (Neon, Supabase) can have cold starts of 5-30s. Cross-region connections add latency per round-trip. pog.query works fine (it passes timeout via pool_options), but pog.transaction fails with QueryTimeout because the checkout path had no configurable timeout.

Usage

let config = pog.url_config(name, url)
  |> pog.checkout_timeout(30_000)  // 30s for serverless DB cold starts

Fixes #82

pog.transaction uses pgo:checkout to acquire a dedicated connection, but
previously called it with no timeout option, defaulting to pgo_pool's
hardcoded 5000ms. This is too short for serverless databases (Neon,
Supabase) with cold starts or cross-region connections with high latency.

Changes:
- Add checkout_timeout field to Config (default: 5000ms for backwards compat)
- Add checkout_timeout/2 setter function
- Store checkout_timeout in Pool connection variant
- Pass timeout to pgo:checkout/2 in transaction path
- Update pog_ffi checkout/2 to accept and forward timeout

Fixes lpil#82
Copilot AI review requested due to automatic review settings April 9, 2026 01:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a configurable connection checkout timeout for pog.transaction, addressing failures on slower/cold-starting Postgres backends by passing an explicit timeout into pgo:checkout/2.

Changes:

  • Added checkout_timeout to pog.Config (default 5000ms) plus a checkout_timeout/2 setter.
  • Extended pog.Connection’s pooled variant to carry checkout_timeout, and used it when acquiring a dedicated connection for transaction.
  • Updated Erlang FFI to support checkout/2 and to match the updated pooled-connection tuple shape.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/pog.gleam Adds config + API surface for checkout timeout and threads it through the transaction checkout path.
src/pog_ffi.erl Implements checkout/2 via pgo:checkout/2 and adapts query tuple matching for the updated Connection representation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

src/pog.gleam Outdated
Comment on lines 42 to 44
Pool(name:, checkout_timeout: 5000)
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named_connection/1 now hardcodes checkout_timeout: 5000, so apps that start a pool with a larger Config.checkout_timeout but later create a Connection via named_connection (as shown in the README) will still have transactions failing at 5s. Consider adding a public API to override the checkout timeout on an existing Connection (or a named_connection/2 variant) so supervised pools can use the configured timeout.

Suggested change
Pool(name:, checkout_timeout: 5000)
}
named_connection_with_timeout(name, 5000)
}
/// Create a reference to a pool using the pool's name and an explicit
/// checkout timeout, in milliseconds.
///
/// This is useful when the pool was started elsewhere with a non-default
/// `Config.checkout_timeout`, so transactions using this connection can use
/// the same timeout when acquiring a dedicated connection from the pool.
///
/// If no pool has been started using this name then queries using this
/// connection will fail.
///
pub fn named_connection_with_timeout(
name: Name(Message),
checkout_timeout: Int,
) -> Connection {
Pool(name:, checkout_timeout:)
}

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 43
pub fn named_connection(name: Name(Message)) -> Connection {
Pool(name)
Pool(name:, checkout_timeout: 5000)
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default checkout timeout value 5000 is duplicated (e.g. named_connection and default_config). To avoid drift if this default changes, consider introducing a single module-level constant (e.g. default_checkout_timeout) and referencing it in both places.

Copilot uses AI. Check for mistakes.
Comment on lines +452 to 457
Pool(name:, checkout_timeout:) -> {
// Check out a single connection from the pool
use #(ref, conn) <- result.try(
checkout(name) |> result.map_error(TransactionQueryError),
checkout(name, checkout_timeout)
|> result.map_error(TransactionQueryError),
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are transaction tests, but none that exercise the new checkout-timeout path (i.e. that checkout_timeout is actually passed through to pgo:checkout/2 and affects transaction behavior). Adding a test that forces checkout contention (e.g. pool_size=1 and holding a connection) would help prevent regressions.

Copilot uses AI. Check for mistakes.
…add test

- Extract default_checkout_timeout constant to avoid magic number duplication
- Add named_connection_with_timeout/2 for supervised pools with custom timeouts
- Add transaction_checkout_timeout_test that verifies short timeout causes
  QueryTimeout when pool is contended (pool_size=1, connection held)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pog.transaction checkout uses hardcoded 5s timeout, ignoring pool config

2 participants