Repository navigation
Conversation
8420b43 to
d671b98
Compare
d671b98 to
656e751
Compare
d500c21 to
2be94a9
Compare
2be94a9 to
c58291b
Compare
|
Just tested this with #2805 and this PR seems to fix that issue 🎉 |
|
Yup, pretty sure it's not flaky. If I test with If I run it with the |
|
The only reason I even checked is this item in your list above:
|
|
It's perfectly possible that this PR doesn't fix all races that can lead to the issue in #2805, but it seems to have at least fixed the race that the reproducer is triggering. |
b6d4757 to
7bb8521
Compare
97c3af6 to
0dd92b4
Compare
0baa295 to
53ad46f
Compare
|
Should this be added to https://github.com/launchbadge/sqlx/milestone/10 ? I would love to get the security toil reduction this would enable, and hate it if this was missed due being forgotten at some critical time :) |
|
This won't be forgotten, but it still needs more work and I don't want to hold the 0.9.0 release much longer. |
Fixes transact-rs#4349. `Floating::return_to_pool` holds the pool's `DecrementSizeGuard` while it does I/O on the connection: an `after_release` hook, a `ping()` to check the connection is still usable, or a graceful `close()`. None of those were bounded. That matters because the I/O can be on a socket that is dead in a way the socket cannot report. If the server disappeared without closing the connection, and the client has nothing left to retransmit, no RST is ever provoked and reads never complete. The returning task then holds its permit forever and the pool shrinks by one connection. After `max_connections` of those, every `acquire()` fails with `PoolTimedOut` and the pool never recovers, even though the server is back. Each step now gets `RETURN_TO_POOL_TIMEOUT` (5s, matching the existing `CLOSE_ON_DROP_TIMEOUT`), and on expiry the connection is dropped via `close_hard()`, which does no I/O. Cancelling `close()` likewise drops the connection and releases the permit. The regression test uses an `after_release` hook that never completes, which is a deterministic stand-in for a socket that never answers: before this change it exhausts `acquire_timeout`, after it the pool is usable again once the bound expires. I realise transact-rs#3582 reworks this area and includes a timeout of its own; this is meant for main until that lands.
Fixes transact-rs#4349. `Floating::return_to_pool` holds the pool's `DecrementSizeGuard` while it does I/O on the connection: an `after_release` hook, a `ping()` to check the connection is still usable, or a graceful `close()`. None of those were bounded. That matters because the I/O can be on a socket that is dead in a way the socket cannot report. If the server disappeared without closing the connection, and the client has nothing left to retransmit, no RST is ever provoked and reads never complete. The returning task then holds its permit forever and the pool shrinks by one connection. After `max_connections` of those, every `acquire()` fails with `PoolTimedOut` and the pool never recovers, even though the server is back. Each step now gets `RETURN_TO_POOL_TIMEOUT` (5s, matching the existing `CLOSE_ON_DROP_TIMEOUT`), and on expiry the connection is dropped via `close_hard()`, which does no I/O. Cancelling `close()` likewise drops the connection and releases the permit. The regression test uses an `after_release` hook that never completes, which is a deterministic stand-in for a socket that never answers: before this change it exhausts `acquire_timeout`, after it the pool is usable again once the bound expires. I realise transact-rs#3582 reworks this area and includes a timeout of its own; this is meant for main until that lands.
Review pointed out that the `ErrorResponse` closing a failed OAUTHBEARER exchange says only that authentication failed. Everything an application needs in order to go and get a token is in the server's status document, which the driver was throwing away, and a token provider cannot be the place where that happens because the document never reaches it. So the document is what comes out now. `Error::OAuth` carries an `OAuthChallenge`, and a connection with no token asks the server for its parameters instead of failing locally: an empty `auth` value, which is how RFC 7628 §4.3 and libpq ask. A missing token and a rejected one therefore end the same way, with the caller holding the issuer and the scope it needs to mint a token and dial again. Nothing re-dials inside `establish`, which has no retries and may be handed a socket by the caller. `oauth_token_provider` goes with it: it could refresh a token but never discover one, and keeping a pool supplied with fresh tokens is the job of the connection callback in transact-rs#3582 rather than of a second mechanism here. `oauth_token` documents the sequence, including that a token set there is not refreshed. `tests/postgres/oauth.rs` covers an accepted token, a rejected one, a connection with no token, a token that would forge a message, and the dial-again sequence as documented, against a real PostgreSQL 18. The `postgres_18_oauth` service compiles a validator module for that, because the server ships none and refuses to run an OAuth exchange without one. Those tests are `#[ignore]`d, since the mechanism is the server's choice and no other service asks for a token; the `postgres-oauth` CI job and `x.py` run them with `--include-ignored`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
acquire()call is cancelled.acquire()should now be completely cancel-safe.PoolConnectortrait superceding bothbefore_connect(requested but not yet implemented) andafter_connectcallbacks.Future, albeit with a'staticrequirement for the returnedFuture(instead ofBoxFuture).usizefor all connection counts to get rid of weird inconsistencies.Breaking Changes
Pool::set_connect_options()andget_connect_options()have been removed. Instead, implement the newPoolConnectortrait (or use a closure) using something likeArc<RwLock<impl ConnectOptions>>.PoolOptions::after_connect()has been removed. Instead, implementPoolConnector(or use a closure), open a connection and then apply any operations necessary.PoolOptions::min_connections(),PoolOptions::max_connections()andPool::size()now useusizeinstead ofu32.Fixes #3513
Fixes #3315
Fixes #3132
Fixes #3117
Fixes #2848