Skip to content

Commit b9de1e0

Browse files
committed
runtime: add test for runtime drop issue
The test fails without the fix and passes with it. It is run with rusty_fork in order to have a fresh process, so that we can set a custom panic hook that aborts the process instead of unwinding. This makes catching panics of tokio worker threads reliable. Tokio worker threads panic if the runtime is dropped from an async context, which is made more likely by the test's two properties: 1) all shared owners of the `Runtime` that are held by the main thread are dropped immediately, which makes more likely that the last owner is held by a tokio worker thread which executes the future. 2) the case is repeated 100 times in a loop, which increases the likelihood of the runtime being dropped from an async context. Without the loop, the test could sometimes pass even without the fix.
1 parent f8532b0 commit b9de1e0

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed

scylla-rust-wrapper/src/runtime.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,95 @@ impl Runtimes {
120120
})
121121
}
122122
}
123+
124+
#[cfg(test)]
125+
mod tests {
126+
use rand::random;
127+
use rusty_fork::rusty_fork_test;
128+
use std::net::SocketAddr;
129+
use tracing::instrument::WithSubscriber as _;
130+
131+
use scylla_proxy::RunningProxy;
132+
133+
use crate::{
134+
argconv::str_to_c_str_n,
135+
cass_error::CassError,
136+
cluster::{cass_cluster_free, cass_cluster_new, cass_cluster_set_contact_points_n},
137+
future::cass_future_free,
138+
session::{cass_session_close, cass_session_connect, cass_session_free, cass_session_new},
139+
testing::{
140+
assert_cass_error_eq, cass_future_wait_check_and_free, mock_init_rules, setup_tracing,
141+
test_with_one_proxy,
142+
},
143+
};
144+
145+
rusty_fork_test! {
146+
#![rusty_fork(timeout_ms = 20000)]
147+
#[test]
148+
/// This test aims to verify that the Tokio runtime used by the driver is never dropped
149+
/// from the async context. Violation of this property results in a panic.
150+
///
151+
/// The property could be violated for example if the last `Arc` reference to the runtime
152+
/// was held by the tokio task that executes the future wrapped by `CassFuture`. Then,
153+
/// dropping the future would drop the runtime, which is forbidden by Tokio as dropping
154+
/// the runtime is a blocking operation.
155+
///
156+
/// The test spawns multiple threads, each of which independently creates a cluster,
157+
/// then sequentially connects a session and disconnects it multiple times.
158+
/// Each connect/disconnect pair is done on a random number of IO threads,
159+
/// bringing chaos to management of the cached runtimes. As a result,
160+
/// this test would likely fail and show a panic due to the runtime being dropped
161+
/// from the async context.
162+
fn runtime_is_never_dropped_in_async_context3() {
163+
setup_tracing();
164+
165+
std::panic::set_hook(Box::new(|panic_info| {
166+
panic_info.payload().downcast_ref::<&str>().map(|s| eprintln!("Panic occurred: {s}"));
167+
std::process::abort();
168+
}));
169+
170+
let runtime = tokio::runtime::Builder::new_current_thread()
171+
.enable_all()
172+
.build()
173+
.unwrap();
174+
175+
runtime.block_on(test_with_one_proxy(
176+
runtime_is_never_dropped_in_async_context_do2,
177+
mock_init_rules(),
178+
)
179+
.with_current_subscriber());
180+
}
181+
}
182+
183+
fn runtime_is_never_dropped_in_async_context_do2(
184+
node_addr: SocketAddr,
185+
proxy: RunningProxy,
186+
) -> RunningProxy {
187+
for _ in 0..100 {
188+
unsafe {
189+
let ip = node_addr.ip().to_string();
190+
let mut cluster_raw = cass_cluster_new();
191+
let (c_ip, c_ip_len) = str_to_c_str_n(ip.as_str());
192+
193+
assert_cass_error_eq!(
194+
cass_cluster_set_contact_points_n(cluster_raw.borrow_mut(), c_ip, c_ip_len),
195+
CassError::CASS_OK
196+
);
197+
let session_raw = cass_session_new();
198+
cass_future_wait_check_and_free(cass_session_connect(
199+
session_raw.borrow(),
200+
cluster_raw.borrow().into_c_const(),
201+
));
202+
203+
let session_close_fut = cass_session_close(session_raw.borrow());
204+
// Intentionally not waiting to increase likelihood of the tokio task dropping the runtime.
205+
// cass_future_wait(session_close_fut.borrow());
206+
cass_future_free(session_close_fut);
207+
208+
cass_cluster_free(cluster_raw);
209+
cass_session_free(session_raw);
210+
}
211+
}
212+
proxy
213+
}
214+
}

0 commit comments

Comments
 (0)