Add configurable checkout timeout for transactions#83
Add configurable checkout timeout for transactions#83
Conversation
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
There was a problem hiding this comment.
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_timeouttopog.Config(default 5000ms) plus acheckout_timeout/2setter. - Extended
pog.Connection’s pooled variant to carrycheckout_timeout, and used it when acquiring a dedicated connection fortransaction. - Updated Erlang FFI to support
checkout/2and 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
| Pool(name:, checkout_timeout: 5000) | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| 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:) | |
| } |
| pub fn named_connection(name: Name(Message)) -> Connection { | ||
| Pool(name) | ||
| Pool(name:, checkout_timeout: 5000) | ||
| } |
There was a problem hiding this comment.
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.
| 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), | ||
| ) |
There was a problem hiding this comment.
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.
…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)
Summary
pog.transactioncalledpgo:checkout/1with no timeout, defaulting to pgo_pool's hardcoded 5000mscheckout_timeoutfield toConfig(default: 5000ms, backwards compatible)checkout_timeout/2setter so users can increase itpgo:checkout/2in the transaction pathProblem
Serverless databases (Neon, Supabase) can have cold starts of 5-30s. Cross-region connections add latency per round-trip.
pog.queryworks fine (it passes timeout viapool_options), butpog.transactionfails withQueryTimeoutbecause the checkout path had no configurable timeout.Usage
Fixes #82