#![cfg(feature = "classifier")]
use std::path::PathBuf;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::{Duration, Instant};
use sqry_nl::classifier::{IntentClassifier, SharedClassifier, TrustMode};
fn in_tree_model_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("models")
}
#[test]
#[ignore = "requires ONNX Runtime dylib + committed model fixtures; run manually with --ignored"]
fn concurrent_classify_does_not_deadlock() {
const THREADS: usize = 10;
const CALLS_PER_THREAD: usize = 100;
const BUDGET: Duration = Duration::from_secs(60);
let model_dir = in_tree_model_dir();
assert!(
model_dir.join("intent_classifier.onnx").exists(),
"expected committed model at {}; install ONNX fixtures \
before running this test",
model_dir.display(),
);
let classifier = IntentClassifier::load(&model_dir, false, TrustMode::Custom)
.expect("in-tree model fixture must load cleanly under strict integrity");
let shared = SharedClassifier::new(classifier);
let barrier = Arc::new(Barrier::new(THREADS));
let start = Instant::now();
let mut handles = Vec::with_capacity(THREADS);
for tid in 0..THREADS {
let shared = shared.clone();
let barrier = Arc::clone(&barrier);
handles.push(thread::spawn(move || {
barrier.wait();
for call in 0..CALLS_PER_THREAD {
let mut guard = shared.lock();
let result = guard
.classify("find all functions that handle authentication")
.expect("classify must succeed against in-tree model");
std::hint::black_box(result);
drop(guard);
if call.is_multiple_of(25) {
eprintln!("thread {tid} progress: {call}/{CALLS_PER_THREAD}");
}
}
}));
}
for h in handles {
h.join()
.expect("worker thread panicked — lock contract broken");
}
let elapsed = start.elapsed();
assert!(
elapsed < BUDGET,
"contention test exceeded liveness budget: {elapsed:?} >= {BUDGET:?}",
);
eprintln!(
"concurrent_classify_does_not_deadlock: {THREADS} threads × \
{CALLS_PER_THREAD} calls completed in {elapsed:?}",
);
}