use educe::Educe;
use futures_util::task::AtomicWaker;
use ouroboros::self_referencing;
use smol::channel::{TryRecvError, TrySendError};
use std::fmt::{Debug, Formatter};
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
use crate::worker::{FindWorkError, FindWorkResult, Work, WorkSource};
#[derive(thiserror::Error, Debug)]
pub enum WorkTaskError {
#[error("work task was cancelled")]
Cancelled,
}
pub type WorkTaskResult<T> = Result<T, WorkTaskError>;
struct WorkRunnable<W: Work + ?Sized> {
work: Box<W>,
sender: smol::channel::Sender<W::Output>,
}
impl<W: Work + ?Sized> Work for WorkRunnable<W> {
type Output = ();
fn execute(self: Box<Self>) -> Self::Output {
let value = self.work.execute();
match self.sender.try_send(value) {
Ok(()) | Err(TrySendError::Closed(_)) => {},
Err(TrySendError::Full(_)) => {
panic!("work task channel should never be full")
}
}
}
}
#[self_referencing]
pub struct WorkTask<T: 'static> {
receiver: smol::channel::Receiver<T>,
#[borrows(receiver)]
#[not_covariant]
recv: Option<Pin<Box<smol::channel::Recv<'this, T>>>>,
}
impl<T: 'static> Debug for WorkTask<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let state_str = self.with_recv(|recv| match recv {
Some(_) => "polling",
None => "incomplete",
});
f.debug_struct("WorkTask")
.field("state", &state_str)
.finish()
}
}
impl<T: 'static> WorkTask<T> {
pub fn spawn<W>(work: Box<W>) -> (Box<dyn Work<Output=()>>, Self)
where
W: Work<Output=T> + ?Sized,
{
let (sender, receiver) = smol::channel::bounded(1);
let runnable = Box::new(WorkRunnable {
work,
sender,
});
let task = WorkTaskBuilder {
receiver,
recv_builder: |_| None,
}.build();
(runnable, task)
}
pub fn try_poll(self) -> Result<WorkTaskResult<T>, Self> {
if self.with_recv(|recv| recv.is_none()) {
match self.borrow_receiver().try_recv() {
Ok(v) => Ok(Ok(v)),
Err(TryRecvError::Empty) => Err(self),
Err(TryRecvError::Closed) => Ok(Err(WorkTaskError::Cancelled)),
}
} else {
panic!("WorkTask must not be try_polled() after it has been polled()")
}
}
}
impl<T> Future for WorkTask<T> {
type Output = WorkTaskResult<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.with_mut(|fields| {
let fut = fields.recv.get_or_insert_with(|| {
Box::pin(fields.receiver.recv())
});
fut.as_mut().poll(cx)
.map(|result| result.map_err(|_| WorkTaskError::Cancelled))
})
}
}
#[derive(Educe)]
#[educe(Default)]
pub struct WorkQueue {
queue: crossbeam::queue::SegQueue<Box<dyn Work<Output=()>>>,
waker: AtomicWaker,
}
impl WorkQueue {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.queue.len()
}
#[inline]
pub fn execute<W: Work>(&self, work: W) -> WorkTask<W::Output> {
self.execute_boxed(Box::new(work))
}
pub fn execute_boxed<W: Work + ?Sized>(&self, work: Box<W>) -> WorkTask<W::Output> {
let (work, task) = WorkTask::spawn(work);
self.queue.push(work);
self.waker.wake();
task
}
}
impl WorkSource for WorkQueue {
#[inline]
fn find_work(&self) -> FindWorkResult {
self.queue.pop().ok_or(FindWorkError::NoWork)
}
#[inline]
fn set_worker_waker(&self, waker: &Waker) {
self.waker.register(waker);
}
}