use std::{
env,
future::{Future, pending},
pin::Pin,
sync::Arc,
task::{Context, Poll, Wake, Waker},
};
use sqlx::{MySqlPool, mysql::MySqlPoolOptions};
struct NoopWake;
impl Wake for NoopWake {
fn wake(self: Arc<Self>) {}
}
fn poll_once<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
let waker = Waker::from(Arc::new(NoopWake));
let mut context = Context::from_waker(&waker);
future.poll(&mut context)
}
async fn pool(url: &str, min_connections: u32, max_connections: u32) -> MySqlPool {
MySqlPoolOptions::new()
.min_connections(min_connections)
.max_connections(max_connections)
.test_before_acquire(false)
.idle_timeout(None)
.max_lifetime(None)
.connect(url)
.await
.unwrap()
}
#[tokio::test(flavor = "current_thread")]
async fn logical_capacity_cannot_use_try_acquire_without_preopened_connections() {
let Ok(url) = env::var("SADDLE_C6_STOP_DATABASE_URL") else {
eprintln!("skipping C6 STOP evidence: SADDLE_C6_STOP_DATABASE_URL is not set");
return;
};
let pool = pool(&url, 0, 2).await;
assert_eq!(pool.options().get_max_connections(), 2);
assert_eq!(
pool.size(),
1,
"sqlx connect opens one connection when min_connections is zero"
);
let mut first = pool.try_acquire().expect("the startup connection is idle");
assert!(
pool.try_acquire().is_none(),
"try_acquire correctly refuses to create or wait for the second physical connection"
);
first.return_to_pool().await;
drop(first);
pool.close().await;
}
#[tokio::test(flavor = "current_thread")]
async fn cancellation_returns_pool_connection_only_through_hidden_spawn() {
let Ok(url) = env::var("SADDLE_C6_STOP_DATABASE_URL") else {
eprintln!("skipping C6 STOP evidence: SADDLE_C6_STOP_DATABASE_URL is not set");
return;
};
let pool = pool(&url, 1, 1).await;
assert_eq!(pool.size(), 1);
assert_eq!(pool.num_idle(), 1);
let execution_pool = pool.clone();
let mut execution = Box::pin(async move {
let mut connection = execution_pool
.try_acquire()
.expect("preopened fixed connection");
let _ = sqlx::query("SELECT SLEEP(60)")
.fetch_optional(&mut *connection)
.await
.unwrap();
pending::<()>().await;
connection.return_to_pool().await;
});
assert!(matches!(poll_once(execution.as_mut()), Poll::Pending));
assert_eq!(pool.num_idle(), 0);
drop(execution);
assert_eq!(
pool.num_idle(),
0,
"the physical slot is not reconciled when the operation Drop returns"
);
for _ in 0..200 {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
if pool.num_idle() == 1 {
break;
}
}
assert_eq!(
pool.num_idle(),
1,
"sqlx's hidden return_to_pool task eventually restores the slot"
);
pool.close().await;
}