use std::sync::Arc;
use std::time::Duration;
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_semaphore_permit_return_basic() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 2,
min_connections: 1,
acquire_timeout: 2000,
..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
for i in 0..10 {
let session = pool
.get_session("admin")
.await
.unwrap_or_else(|_| panic!("Failed to get session on iteration {}", i));
drop(session);
tokio::time::sleep(Duration::from_millis(10)).await;
}
let status = pool.status();
assert_eq!(status.active, 0, "All sessions should be released");
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_semaphore_permit_return_concurrent() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 5,
min_connections: 1,
acquire_timeout: 5000,
..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
let iterations = 50;
let mut handles = Vec::new();
for _ in 0..iterations {
let pool_clone = pool.clone();
let handle = tokio::spawn(async move {
let session = pool_clone.get_session("admin").await.expect("Failed to get session");
tokio::time::sleep(Duration::from_millis(1)).await;
drop(session);
});
handles.push(handle);
}
for handle in handles {
handle.await.expect("Task panicked");
}
tokio::time::sleep(Duration::from_millis(100)).await;
let status = pool.status();
assert_eq!(
status.active, 0,
"All sessions should be released after concurrent operations"
);
assert!(
status.borrow_count >= iterations as u64,
"All borrows should be counted"
);
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_no_deadlock_high_concurrency() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 3,
min_connections: 1,
acquire_timeout: 10000, ..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
let rounds = 10;
let concurrent_tasks = 10;
for round in 0..rounds {
let mut handles = Vec::new();
for _ in 0..concurrent_tasks {
let pool_clone = pool.clone();
let handle = tokio::spawn(async move {
match tokio::time::timeout(Duration::from_secs(5), pool_clone.get_session("admin")).await {
Ok(Ok(session)) => {
let status = pool_clone.status();
assert!(status.active <= 5, "Active connections should be bounded");
tokio::time::sleep(Duration::from_micros(100)).await;
drop(session);
true
}
_ => false,
}
});
handles.push(handle);
}
let results: Vec<_> = futures::future::join_all(handles).await;
let success_count = results.iter().filter(|r| *r.as_ref().unwrap_or(&false)).count();
assert!(
success_count >= 1,
"Round {}: At least some tasks should succeed",
round
);
tokio::time::sleep(Duration::from_millis(20)).await;
}
let status = pool.status();
assert_eq!(status.active, 0, "All sessions should be released after all rounds");
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_semaphore_permit_reuse_after_max() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 2,
min_connections: 1,
acquire_timeout: 3000,
..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
let session1 = pool.get_session("admin").await.expect("Failed to get session 1");
let session2 = pool.get_session("admin").await.expect("Failed to get session 2");
let status = pool.status();
assert_eq!(status.active, 2, "Should have 2 active connections");
drop(session1);
tokio::time::sleep(Duration::from_millis(50)).await;
let session3 = pool
.get_session("admin")
.await
.expect("Should be able to get new connection after release");
let status = pool.status();
assert_eq!(status.active, 2, "Should have 2 active connections again");
drop(session2);
drop(session3);
tokio::time::sleep(Duration::from_millis(50)).await;
let status = pool.status();
assert_eq!(status.active, 0, "All sessions should be released");
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_pool_stress_rapid_acquire_release() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 10,
min_connections: 1,
acquire_timeout: 5000,
..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
let total_operations = 500;
let mut handles = Vec::new();
for _ in 0..total_operations {
let pool_clone = pool.clone();
let handle = tokio::spawn(async move {
let session = pool_clone.get_session("admin").await.ok()?;
drop(session);
Some(())
});
handles.push(handle);
}
let results: Vec<_> = futures::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap_or(&None).is_some()).count();
tokio::time::sleep(Duration::from_millis(200)).await;
let status = pool.status();
assert_eq!(status.active, 0, "All sessions should be released after stress test");
assert!(success_count > 0, "At least some operations should succeed");
let session = pool
.get_session("admin")
.await
.expect("Pool should still be usable after stress test");
drop(session);
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_semaphore_fairness() {
let config = dbnexus::DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 2,
min_connections: 1,
acquire_timeout: 5000,
..Default::default()
};
let pool = Arc::new(
dbnexus::DbPool::with_config(config)
.await
.expect("Failed to create pool"),
);
let session1 = pool.get_session("admin").await.expect("Failed to get session 1");
let session2 = pool.get_session("admin").await.expect("Failed to get session 2");
let pool_clone = pool.clone();
let wait_task1 = tokio::spawn(async move {
let result = pool_clone.get_session("admin").await.ok();
tokio::time::sleep(Duration::from_millis(50)).await;
drop(result);
});
let pool_clone = pool.clone();
let wait_task2 = tokio::spawn(async move {
let result = pool_clone.get_session("admin").await.ok();
tokio::time::sleep(Duration::from_millis(50)).await;
drop(result);
});
tokio::time::sleep(Duration::from_millis(50)).await;
drop(session1);
drop(session2);
let result1 = tokio::time::timeout(Duration::from_secs(3), wait_task1).await;
let result2 = tokio::time::timeout(Duration::from_secs(3), wait_task2).await;
assert!(
result1.is_ok() || result2.is_ok(),
"At least one waiting task should succeed"
);
tokio::time::sleep(Duration::from_millis(200)).await;
let status = pool.status();
assert_eq!(status.active, 0, "All sessions should be released");
}