Skip to content

Commit adf02e1

Browse files
authored
RUST-714 Fix possible deadlock during retry (1.x driver) (#317)
1 parent d75355f commit adf02e1

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

src/client/executor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ impl Client {
125125
Retryability::Write => get_error_with_retryable_write_label(&conn, err).await?,
126126
_ => err,
127127
};
128+
// release the connection to be processed by the connection pool
129+
drop(conn);
128130

129131
// TODO RUST-90: Do not retry read if session is in a transaction
130132
if retryability == Retryability::Read && err.is_read_retryable()

src/test/spec/retryable_reads.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,59 @@
1+
use std::time::Duration;
2+
3+
use bson::doc;
14
use tokio::sync::RwLockWriteGuard;
25

3-
use crate::test::{run_spec_test, run_v2_test, LOCK};
6+
use crate::{
7+
test::{
8+
run_spec_test,
9+
run_v2_test,
10+
FailCommandOptions,
11+
FailPoint,
12+
FailPointMode,
13+
TestClient,
14+
CLIENT_OPTIONS,
15+
LOCK,
16+
},
17+
RUNTIME,
18+
};
419

520
#[cfg_attr(feature = "tokio-runtime", tokio::test(threaded_scheduler))]
621
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
722
async fn run() {
823
let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await;
924
run_spec_test(&["retryable-reads"], run_v2_test).await;
1025
}
26+
27+
/// Test ensures that the connection used in the first attempt of a retry is released back into the
28+
/// pool before the second attempt.
29+
#[cfg_attr(feature = "tokio-runtime", tokio::test(threaded_scheduler))]
30+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
31+
async fn retry_releases_connection() {
32+
let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await;
33+
34+
let mut client_options = CLIENT_OPTIONS.clone();
35+
client_options.hosts.drain(1..);
36+
client_options.retry_reads = Some(true);
37+
client_options.max_pool_size = Some(1);
38+
39+
let client = TestClient::with_options(Some(client_options), true).await;
40+
if !client.supports_fail_command().await {
41+
println!("skipping retry_releases_connection due to failCommand not being supported");
42+
return;
43+
}
44+
45+
let collection = client
46+
.database("retry_releases_connection")
47+
.collection("retry_releases_connection");
48+
collection.insert_one(doc! { "x": 1 }, None).await.unwrap();
49+
50+
let options = FailCommandOptions::builder().error_code(91).build();
51+
let failpoint = FailPoint::fail_command(&["find"], FailPointMode::Times(1), Some(options));
52+
let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap();
53+
54+
RUNTIME
55+
.timeout(Duration::from_secs(1), collection.find_one(doc! {}, None))
56+
.await
57+
.expect("operation should not time out")
58+
.expect("find should succeed");
59+
}

0 commit comments

Comments
 (0)