Skip to content

feat(pool): add connect_timeout option for spawned connection tasks - #4369

Open
mmustafasenoglu wants to merge 2 commits into
transact-rs:mainfrom
mmustafasenoglu:fix/pool-spawn-connect-task
Open

feat(pool): add connect_timeout option for spawned connection tasks#4369
mmustafasenoglu wants to merge 2 commits into
transact-rs:mainfrom
mmustafasenoglu:fix/pool-spawn-connect-task

Conversation

@mmustafasenoglu

Copy link
Copy Markdown

Description

This PR adds a new connect_timeout option to PoolOptions that, when set, spawns the connection attempt as a separate task in Pool::acquire(). This ensures that if acquire() is cancelled or times out, the connection attempt continues in the background rather than being aborted.

If the connection succeeds but no one picks it up (because acquire() was cancelled), the connection is automatically returned to the pool's idle queue.

Problem

Currently, acquire() wraps both the semaphore/idle-connection check and the actual connection creation in a single timeout. If acquire() is cancelled (e.g., due to its own timeout), the in-flight connect() call is also aborted. Under high contention, this can cause:

  • Connection churn: connections are created and immediately discarded
  • Pool instability: DecrementSizeGuard drops, pool size fluctuates
  • Cascading timeouts: other waiting tasks also time out

This is the root cause behind several reported issues: #3315, #3132, #2848.

Solution

When connect_timeout is set on PoolOptions:

  1. The connection attempt is spawned as a separate async task via crate::rt::spawn
  2. The spawned task uses its own deadline based on connect_timeout
  3. A futures_intrusive::channel::oneshot channel communicates the result back
  4. If acquire() is cancelled (receiver dropped), the sender detects this and returns the connection to the pool's idle queue

When connect_timeout is not set (the default), the existing behavior is preserved exactly -- no breaking changes.

Changes

  • sqlx-core/src/pool/options.rs: Added connect_timeout: Option<Duration> field, builder method, getter, and updated Debug/Clone impls
  • sqlx-core/src/pool/inner.rs: Modified acquire() to spawn connection as a separate task when connect_timeout is set

Usage

use sqlx::postgres::PgPoolOptions;
use std::time::Duration;

let pool = PgPoolOptions::new()
    .max_connections(10)
    .acquire_timeout(Duration::from_secs(5))
    .connect_timeout(Duration::from_secs(10))  // NEW
    .connect("postgres://localhost/mydb")
    .await?;

Notes

  • Uses futures_intrusive::channel::oneshot_channel for cross-runtime compatibility (works with tokio, async-std, smol)
  • No new dependencies added
  • Not a breaking change -- opt-in via new optional field
  • Default behavior unchanged when connect_timeout is not set

Closes #3513

When connect_timeout is set, Pool::acquire() spawns the connection
attempt as a separate task instead of running it inline. This ensures
that if acquire() is cancelled or times out, the connection attempt
continues in the background. If it succeeds, the connection is
returned to the pool's idle queue.

This addresses the issue where acquire() cancellation would abort
in-flight connection attempts, causing connection churn under high
contention (see transact-rs#3315, transact-rs#3132, transact-rs#2848).

Closes transact-rs#3513
Copilot AI lite review requested due to automatic review settings August 9, 2026 18:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

- Import path: futures_intrusive::channel::shared::oneshot_channel (not ::channel::oneshot_channel)
- ChannelSendError<T> wrapping: destructure with Err(ChannelSendError(result))
- rx.receive().await returns Option<T>, use ok_or(Error::PoolTimedOut)

Fixes CI failures in sqlx transact-rs#4369.
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.

Pool should spawn a task when connecting in acquire()

2 participants