use core::fmt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use std::time::Duration;
use crate::engine::{DetachedRequest, Family, Service};
use crate::envelope::Envelope;
use crate::hooks::Dispatch;
use crate::reply::{CompleteError, Reply, ReplyToken};
use crate::service::{Outcome, WorkClass};
const COMPLETE_RETRY: Duration = Duration::from_millis(1);
pub trait OffloadPool: Send + Sync + 'static {
fn try_submit(&self, class: WorkClass, job: OffloadJob) -> Result<(), OffloadJob>;
}
pub struct OffloadJob {
class: WorkClass,
body: Box<dyn JobBody>,
}
impl OffloadJob {
#[must_use]
pub fn class(&self) -> WorkClass {
self.class
}
pub fn run(self) {
self.body.run();
}
}
impl fmt::Debug for OffloadJob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OffloadJob")
.field("class", &self.class)
.finish_non_exhaustive()
}
}
trait JobBody: Send {
fn run(self: Box<Self>);
fn cancel(self: Box<Self>);
}
pub(crate) trait JobService<F: Family>: Send + Sync {
fn detach(&self, request: &F::Request<'_>) -> Option<Box<dyn DetachedRequest<F>>>;
fn call(&self, request: &F::Request<'_>, env: &Envelope, reply: &mut Reply<F>) -> Outcome;
fn panicked(&self, env: &Envelope);
}
struct Job<F: Family> {
request: Box<dyn DetachedRequest<F>>,
env: Envelope,
token: ReplyToken<F>,
service: Arc<dyn JobService<F>>,
}
impl<F: Family> JobBody for Job<F> {
fn run(self: Box<Self>) {
let Job {
request,
env,
mut token,
service,
} = *self;
if token.is_cancelled() {
return;
}
token.link_reply();
let reply = token.reply();
let mut outcome = None;
let called = catch_unwind(AssertUnwindSafe(|| {
request.with(&mut |r| outcome = Some(service.call(r, &env, &mut *reply)));
}));
if called.is_err() {
service.panicked(&env);
return;
}
if token.reply().deferred {
token.release();
return;
}
let close = match outcome {
Some(Outcome::Done) => false,
Some(Outcome::Close) => true,
_ => return,
};
loop {
match token.finish(close) {
Err(CompleteError::Busy(back)) => token = back,
_ => return,
}
std::thread::sleep(COMPLETE_RETRY);
}
}
fn cancel(self: Box<Self>) {
let mut job = *self;
job.token.release_to_idle();
}
}
#[derive(Debug)]
pub struct Offload<P: OffloadPool> {
pool: P,
}
impl<P: OffloadPool> Offload<P> {
pub fn new(pool: P) -> Self {
Offload { pool }
}
}
impl<P: OffloadPool> Dispatch for Offload<P> {
fn run<F: Family, S: Service<F>>(
&self,
service: &S,
request: &F::Request<'_>,
env: &Envelope,
reply: &mut Reply<F>,
) -> Outcome {
if S::WORK == WorkClass::Quick {
return service.call(request, env, reply);
}
let detached = reply
.job
.as_ref()
.and_then(|job| Some((job.detach(request)?, Arc::clone(job))));
let Some((copy, handle)) = detached else {
return service.call(request, env, reply);
};
let job = OffloadJob {
class: S::WORK,
body: Box::new(Job {
request: copy,
env: env.clone(),
token: reply.defer(),
service: handle,
}),
};
match self.pool.try_submit(S::WORK, job) {
Ok(()) => Outcome::Deferred,
Err(job) => {
job.body.cancel();
reply.deferred = false;
reply.pool_full = true;
Outcome::Close
}
}
}
}