use std::sync::Arc;
use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
use std::sync::mpsc;
use std::time::Duration;
use super::expr::MbaExpr;
use super::solve::{MbaAnswer, MbaBudget, MbaSolver};
pub struct ThreadedSolver<S> {
inner: Arc<S>,
timeout: Duration,
max_abandoned: usize,
abandoned: Arc<AtomicUsize>,
id: String,
}
impl<S: MbaSolver + 'static> ThreadedSolver<S> {
pub fn new(inner: S, timeout: Duration, max_abandoned: usize) -> Self {
let id = inner.id().to_string();
ThreadedSolver {
inner: Arc::new(inner),
timeout,
max_abandoned,
abandoned: Arc::new(AtomicUsize::new(0)),
id,
}
}
pub fn abandoned(&self) -> usize {
self.abandoned.load(Ordering::Acquire)
}
}
impl<S> core::fmt::Debug for ThreadedSolver<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ThreadedSolver")
.field("id", &self.id)
.field("timeout", &self.timeout)
.field("max_abandoned", &self.max_abandoned)
.finish_non_exhaustive()
}
}
impl<S: MbaSolver + 'static> MbaSolver for ThreadedSolver<S> {
fn id(&self) -> &str {
&self.id
}
fn polynomial_fragments(&self) -> bool {
self.inner.polynomial_fragments()
}
fn solve(&self, p: &MbaExpr, budget: &MbaBudget) -> MbaAnswer {
const RUNNING: u8 = 0;
const DONE: u8 = 1;
const ABANDONED: u8 = 2;
if self.abandoned.load(Ordering::Acquire) >= self.max_abandoned {
return MbaAnswer::Exhausted;
}
let (tx, rx) = mpsc::channel();
let (inner, p, budget) = (self.inner.clone(), p.clone(), *budget);
let abandoned = self.abandoned.clone();
let state = Arc::new(AtomicU8::new(RUNNING));
let st = state.clone();
let spawned = std::thread::Builder::new()
.name("bitwright-mba".into())
.spawn(move || {
let answer = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
inner.solve(&p, &budget)
}))
.unwrap_or_else(|_| MbaAnswer::Unsupported("the solver panicked".into()));
let _ = tx.send(answer);
if st
.compare_exchange(RUNNING, DONE, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
abandoned.fetch_sub(1, Ordering::AcqRel);
}
});
if spawned.is_err() {
return MbaAnswer::Exhausted;
}
match rx.recv_timeout(self.timeout) {
Ok(a) => a,
Err(_) => {
self.abandoned.fetch_add(1, Ordering::AcqRel);
if state
.compare_exchange(RUNNING, ABANDONED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
MbaAnswer::Exhausted
} else {
self.abandoned.fetch_sub(1, Ordering::AcqRel);
rx.recv().unwrap_or(MbaAnswer::Exhausted)
}
}
}
}
}