#![cfg(feature = "native-db")]
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use atomic_websocket::common::{get_setting_by_key, set_setting};
use atomic_websocket::external::native_db::{Builder, Models};
use atomic_websocket::types::DB;
use atomic_websocket::Settings;
use tempfile::NamedTempFile;
use tokio::sync::Mutex;
fn make_native_db() -> (NamedTempFile, DB) {
let mut models = Models::new();
models.define::<Settings>().unwrap();
let models: &'static Models = Box::leak(Box::new(models));
let temp = NamedTempFile::new().unwrap();
let db = Builder::new().create(models, temp.path()).unwrap();
(temp, Arc::new(Mutex::new(db)))
}
#[tokio::test(flavor = "current_thread")]
async fn db_writes_do_not_starve_single_thread_runtime() {
let (_temp, db) = make_native_db();
let ticks = Arc::new(AtomicU64::new(0));
let ticks_clone = ticks.clone();
let ticker = tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_millis(5)).await;
ticks_clone.fetch_add(1, Ordering::Relaxed);
}
});
let start = Instant::now();
let mut handles = Vec::new();
for i in 0..200u32 {
let db = db.clone();
handles.push(tokio::spawn(async move {
set_setting(
db,
Settings {
key: format!("key-{i}"),
value: i.to_le_bytes().to_vec(),
},
)
.await
.expect("set_setting should succeed");
}));
}
for h in handles {
h.await.expect("write task panicked");
}
let elapsed = start.elapsed();
ticker.abort();
let observed = ticks.load(Ordering::Relaxed);
let theoretical = (elapsed.as_millis() / 5) as u64;
assert!(
observed * 4 >= theoretical,
"runtime starved during DB writes: {observed} ticks in {elapsed:?} \
(theoretical {theoretical}); DB fsync is likely blocking the worker thread"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn db_concurrent_writes_are_correct_and_deadlock_free() {
let (_temp, db) = make_native_db();
const N: u32 = 100;
let mut writers = Vec::new();
for i in 0..N {
let db = db.clone();
writers.push(tokio::spawn(async move {
set_setting(
db,
Settings {
key: format!("k-{i}"),
value: i.to_le_bytes().to_vec(),
},
)
.await
.expect("write should succeed");
}));
}
let writers_done = async {
for w in writers {
w.await.expect("writer panicked");
}
};
tokio::time::timeout(Duration::from_secs(30), writers_done)
.await
.expect("concurrent writes deadlocked or timed out");
for i in 0..N {
let got = get_setting_by_key(db.clone(), format!("k-{i}"))
.await
.expect("read should succeed")
.expect("key should exist");
assert_eq!(got.value, i.to_le_bytes().to_vec(), "value mismatch for k-{i}");
}
}