use crate::bug_message::BUG_MESSAGE;
use crate::runtime::Config;
use crate::sync::{AsyncChannel, AsyncReceiver, AsyncSender, Channel, RecvResult, SendResult};
use crate::{local_executor, Executor};
use crossbeam::queue::SegQueue;
use std::future::Future;
use std::panic::UnwindSafe;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Poll;
use std::{panic, ptr, thread};
struct Result {
future_result: thread::Result<()>,
sender: Arc<Channel<Job>>,
}
unsafe impl Send for Result {}
type ResultSender = Arc<Channel<Result>>;
struct Job {
future: Box<dyn Future<Output = ()> + UnwindSafe>,
sender: Option<Arc<Channel<Job>>>,
result_sender: ResultSender,
}
impl Job {
pub(crate) fn new<Fut: Future<Output = ()> + UnwindSafe + 'static>(
future: Fut,
channel: Arc<Channel<Self>>,
result_channel: ResultSender,
) -> Self {
Self {
future: Box::new(future),
sender: Some(channel),
result_sender: result_channel,
}
}
}
impl Future for Job {
type Output = ();
fn poll(self: Pin<&mut Self>, mut cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let mut unwind_safe_cx = panic::AssertUnwindSafe(&mut cx);
let mut unwind_safe_future =
{ panic::AssertUnwindSafe(ptr::from_mut(this.future.as_mut())) };
let handle = panic::catch_unwind(move || {
let pinned_future = unsafe { Pin::new_unchecked(&mut **unwind_safe_future) };
pinned_future.poll(*unwind_safe_cx)
});
if let Ok(poll_res) = handle {
if poll_res.is_ready() {
let sender = this.sender.take().unwrap();
local_executor().exec_shared_future(async move {
let send_res = this
.result_sender
.send(Result {
future_result: Ok(()),
sender,
})
.await;
assert!(matches!(send_res, SendResult::Ok), "{BUG_MESSAGE}");
});
return Poll::Ready(());
}
Poll::Pending
} else {
let sender = this.sender.take().unwrap();
local_executor().exec_shared_future(async move {
let send_res = this
.result_sender
.send(Result {
future_result: Err(Box::new(handle.unwrap_err())),
sender,
})
.await;
assert!(matches!(send_res, SendResult::Ok), "{BUG_MESSAGE}");
});
Poll::Ready(())
}
}
}
#[allow(
clippy::non_send_fields_in_send_ty,
reason = "We guarantee that `Job` is `Send`"
)]
unsafe impl Send for Job {}
pub struct ExecutorPoolJoinHandle {
was_joined: bool,
channel: ResultSender,
pool: &'static ExecutorPool,
}
impl ExecutorPoolJoinHandle {
fn new(channel: ResultSender, pool: &'static ExecutorPool) -> Self {
Self {
was_joined: false,
channel,
pool,
}
}
pub async fn join(mut self) {
self.was_joined = true;
let res = self.channel.recv().await.unwrap();
self.pool.senders_to_executors.push(res.sender);
if let Err(err) = res.future_result {
panic::resume_unwind(err);
}
}
}
unsafe impl Send for ExecutorPoolJoinHandle {}
impl Drop for ExecutorPoolJoinHandle {
fn drop(&mut self) {
assert!(
self.was_joined,
"ExecutorPoolJoinHandle::join() must be called! \
If you don't want to wait result immediately, put it somewhere and join it later."
);
}
}
pub struct ExecutorPool {
senders_to_executors: SegQueue<Arc<Channel<Job>>>,
}
unsafe impl Send for ExecutorPool {}
fn executor_pool_cfg() -> Config {
Config::default().disable_work_sharing()
}
static EXECUTOR_POOL: ExecutorPool = ExecutorPool::new();
impl ExecutorPool {
pub(crate) const fn new() -> Self {
Self {
senders_to_executors: SegQueue::new(),
}
}
fn new_executor() -> Arc<Channel<Job>> {
let channel = Arc::new(Channel::bounded(0));
let channel_clone = channel.clone();
thread::spawn(move || {
let ex = Executor::init_with_config(executor_pool_cfg());
ex.run_and_block_on_shared(async move {
while let RecvResult::Ok(job) = channel_clone.recv().await {
job.await;
}
})
.expect(BUG_MESSAGE);
});
channel
}
#[allow(
clippy::missing_panics_doc,
reason = "It panics only when a bug is occurred"
)]
pub async fn sched_future<Fut>(future: Fut) -> ExecutorPoolJoinHandle
where
Fut: Future<Output = ()> + Send + 'static + UnwindSafe,
{
let result_channel = Arc::new(Channel::bounded(0));
let sender = EXECUTOR_POOL
.senders_to_executors
.pop()
.unwrap_or_else(Self::new_executor);
let send_res = sender
.send(Job::new(future, sender.clone(), result_channel.clone()))
.await;
assert!(matches!(send_res, SendResult::Ok), "{BUG_MESSAGE}");
ExecutorPoolJoinHandle::new(result_channel, &EXECUTOR_POOL)
}
}
pub fn sched_future_to_another_thread<Fut>(future: Fut)
where
Fut: Future<Output = ()> + Send + 'static + UnwindSafe,
{
local_executor().exec_shared_future(async move {
let handle = ExecutorPool::sched_future(future).await;
handle.join().await;
});
}