Skip to content

Commit 6a8149c

Browse files
committed
pool: fix panic when using callbacks
add regression test
1 parent 339e058 commit 6a8149c

File tree

2 files changed

+129
-2
lines changed

2 files changed

+129
-2
lines changed

sqlx-core/src/pool/connection.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,10 @@ impl<DB: Database> Floating<DB, Idle<DB>> {
325325
let now = Instant::now();
326326

327327
PoolConnectionMetadata {
328-
age: self.created_at.duration_since(now),
329-
idle_for: self.idle_since.duration_since(now),
328+
// NOTE: the receiver is the later `Instant` and the arg is the earlier
329+
// https://github.com/launchbadge/sqlx/issues/1912
330+
age: now.saturating_duration_since(self.created_at),
331+
idle_for: now.saturating_duration_since(self.idle_since),
330332
}
331333
}
332334
}

tests/any/pool.rs

+125
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use sqlx::any::AnyPoolOptions;
2+
use sqlx::Executor;
3+
use std::sync::atomic::AtomicI32;
24
use std::sync::{
35
atomic::{AtomicUsize, Ordering},
46
Arc,
@@ -64,3 +66,126 @@ async fn pool_should_be_returned_failed_transactions() -> anyhow::Result<()> {
6466

6567
Ok(())
6668
}
69+
70+
#[sqlx_macros::test]
71+
async fn test_pool_callbacks() -> anyhow::Result<()> {
72+
sqlx_test::setup_if_needed();
73+
74+
#[derive(sqlx::FromRow, Debug, PartialEq, Eq)]
75+
struct ConnStats {
76+
id: i32,
77+
before_acquire_calls: i32,
78+
after_release_calls: i32,
79+
}
80+
81+
let current_id = AtomicI32::new(0);
82+
83+
let pool = AnyPoolOptions::new()
84+
.max_connections(1)
85+
.acquire_timeout(Duration::from_secs(5))
86+
.after_connect(move |conn, meta| {
87+
assert_eq!(meta.age, Duration::ZERO);
88+
assert_eq!(meta.idle_for, Duration::ZERO);
89+
90+
let id = current_id.fetch_add(1, Ordering::AcqRel);
91+
92+
Box::pin(async move {
93+
let statement = format!(
94+
// language=SQL
95+
r#"
96+
CREATE TEMPORARY TABLE conn_stats(
97+
id int primary key,
98+
before_acquire_calls int default 0,
99+
after_release_calls int default 0
100+
);
101+
INSERT INTO conn_stats(id) VALUES ({});
102+
"#,
103+
// Until we have generalized bind parameters
104+
id
105+
);
106+
107+
conn.execute(&statement[..]).await?;
108+
Ok(())
109+
})
110+
})
111+
.before_acquire(|conn, meta| {
112+
// `age` and `idle_for` should both be nonzero
113+
assert_ne!(meta.age, Duration::ZERO);
114+
assert_ne!(meta.idle_for, Duration::ZERO);
115+
116+
Box::pin(async move {
117+
let stats: ConnStats = sqlx::query_as(
118+
r#"
119+
UPDATE conn_stats
120+
SET before_acquire_calls = before_acquire_calls + 1
121+
RETURNING *
122+
"#,
123+
)
124+
.fetch_one(conn)
125+
.await?;
126+
127+
// For even IDs, cap by the number of before_acquire calls.
128+
// Ignore the check for odd IDs.
129+
Ok((stats.id & 1) == 1 || stats.before_acquire_calls < 3)
130+
})
131+
})
132+
.after_release(|conn, meta| {
133+
// `age` should be nonzero but `idle_for` should be zero.
134+
assert_ne!(meta.age, Duration::ZERO);
135+
assert_eq!(meta.idle_for, Duration::ZERO);
136+
137+
Box::pin(async move {
138+
let stats: ConnStats = sqlx::query_as(
139+
r#"
140+
UPDATE conn_stats
141+
SET after_release_calls = after_release_calls + 1
142+
RETURNING *
143+
"#,
144+
)
145+
.fetch_one(conn)
146+
.await?;
147+
148+
// For odd IDs, cap by the number of before_release calls.
149+
// Ignore the check for even IDs.
150+
Ok((stats.id & 1) == 0 || stats.after_release_calls < 4)
151+
})
152+
})
153+
// Don't establish a connection yet.
154+
.connect_lazy(&dotenv::var("DATABASE_URL")?)?;
155+
156+
// Expected pattern of (id, before_acquire_calls, after_release_calls)
157+
let pattern = [
158+
// The connection pool starts empty.
159+
(0, 0, 0),
160+
(0, 1, 1),
161+
(0, 2, 2),
162+
(1, 0, 0),
163+
(1, 1, 1),
164+
(1, 2, 2),
165+
// We should expect one more `acquire` because the ID is odd
166+
(1, 3, 3),
167+
(2, 0, 0),
168+
(2, 1, 1),
169+
(2, 2, 2),
170+
(3, 0, 0),
171+
];
172+
173+
for (id, before_acquire_calls, after_release_calls) in pattern {
174+
let conn_stats: ConnStats = sqlx::query_as("SELECT * FROM conn_stats")
175+
.fetch_one(&pool)
176+
.await?;
177+
178+
assert_eq!(
179+
conn_stats,
180+
ConnStats {
181+
id,
182+
before_acquire_calls,
183+
after_release_calls
184+
}
185+
);
186+
}
187+
188+
pool.close().await;
189+
190+
Ok(())
191+
}

0 commit comments

Comments
 (0)