use super::task::{Task, TaskCanceledError};
use crate::ObservableError;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex, PoisonError};
use std::task::{Context, Poll, Wake, Waker};
use std::thread::ThreadId;
pub type ThreadPoster = Arc<dyn Fn(Box<dyn FnOnce() + Send>) + Send + Sync>;
#[derive(Clone, Copy)]
enum Pending {
Task(u64),
Callback(u64),
}
pub(crate) struct Inbox {
queue: Mutex<Vec<Pending>>,
arrived: Condvar,
poster: Mutex<Option<ThreadPoster>>,
}
impl Inbox {
fn push(&self, pending: Pending) {
self.queue.lock().unwrap_or_else(PoisonError::into_inner).push(pending);
self.arrived.notify_all();
let poster = self.poster.lock().unwrap_or_else(PoisonError::into_inner).clone();
if let Some(poster) = poster {
poster(Box::new(|| {
run_pending_wakes();
}));
}
}
fn take(&self) -> Vec<Pending> {
std::mem::take(&mut *self.queue.lock().unwrap_or_else(PoisonError::into_inner))
}
}
struct LocalTask {
future: RefCell<Option<Pin<Box<dyn Future<Output = ()>>>>>,
polling: Cell<bool>,
repoll: Cell<bool>,
waker: Waker,
}
crate::ferro_thread_local! {
static TASKS: RefCell<HashMap<u64, Rc<LocalTask>>> = RefCell::new(HashMap::new());
static INBOX: Arc<Inbox> = Arc::new(Inbox { queue: Mutex::new(Vec::new()), arrived: Condvar::new(), poster: Mutex::new(None) });
static CALLBACKS: RefCell<HashMap<u64, Box<dyn FnOnce()>>> = RefCell::new(HashMap::new());
}
static NEXT_CALLBACK: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
pub(crate) struct LocalCallback {
id: u64,
thread: ThreadId,
inbox: Arc<Inbox>,
_thread_bound: std::marker::PhantomData<Rc<()>>,
}
impl LocalCallback {
pub(crate) fn register(callback: Box<dyn FnOnce()>) -> Self {
let id = NEXT_CALLBACK.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
CALLBACKS.with_borrow_mut(|callbacks| callbacks.insert(id, callback));
Self { id, thread: std::thread::current().id(), inbox: INBOX.with(Arc::clone), _thread_bound: std::marker::PhantomData }
}
pub(crate) fn handle(&self) -> LocalCallbackHandle {
LocalCallbackHandle { id: self.id, thread: self.thread, inbox: self.inbox.clone() }
}
pub(crate) fn remove(&self) {
let removed = CALLBACKS.try_with(|callbacks| callbacks.borrow_mut().remove(&self.id)).ok().flatten();
drop(removed);
}
}
pub(crate) struct LocalCallbackHandle {
id: u64,
thread: ThreadId,
inbox: Arc<Inbox>,
}
impl LocalCallbackHandle {
pub(crate) fn run(&self) {
if std::thread::current().id() == self.thread {
run_callback(self.id);
} else {
self.inbox.push(Pending::Callback(self.id));
}
}
}
fn run_callback(id: u64) {
let callback = CALLBACKS.try_with(|callbacks| callbacks.borrow_mut().remove(&id)).ok().flatten();
if let Some(callback) = callback {
callback();
}
}
struct TaskWaker {
task_id: u64,
thread: ThreadId,
inbox: Arc<Inbox>,
}
impl Wake for TaskWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
if std::thread::current().id() == self.thread {
poll_task(self.task_id);
} else {
self.inbox.push(Pending::Task(self.task_id));
}
}
}
fn poll_task(task_id: u64) {
let Some(task) = TASKS.with_borrow(|tasks| tasks.get(&task_id).cloned()) else { return };
if task.polling.get() {
task.repoll.set(true);
return;
}
loop {
task.polling.set(true);
task.repoll.set(false);
let done = {
let mut slot = task.future.borrow_mut();
let Some(future) = slot.as_mut() else { break };
let mut context = Context::from_waker(&task.waker);
let done = future.as_mut().poll(&mut context).is_ready();
if done {
slot.take();
}
done
};
task.polling.set(false);
if done {
TASKS.with_borrow_mut(|tasks| tasks.remove(&task_id));
break;
}
if !task.repoll.get() {
break;
}
}
task.polling.set(false);
}
pub fn spawn_local<T: 'static>(future: impl Future<Output = Result<T, ObservableError>> + 'static) -> Task<T> {
let task = Task::with_status(super::TaskStatus::Running);
let target = task.clone();
let id = task.id();
let inbox = INBOX.with(Arc::clone);
let waker = Waker::from(Arc::new(TaskWaker { task_id: id, thread: std::thread::current().id(), inbox }));
let local = Rc::new(LocalTask {
future: RefCell::new(Some(Box::pin(async move {
let outcome = future.await;
target.try_complete(outcome);
}))),
polling: Cell::new(false),
repoll: Cell::new(false),
waker,
});
TASKS.with_borrow_mut(|tasks| tasks.insert(id, local));
poll_task(id);
task
}
pub fn set_thread_poster(poster: Option<ThreadPoster>) {
let inbox = INBOX.with(Arc::clone);
*inbox.poster.lock().unwrap_or_else(PoisonError::into_inner) = poster;
if !inbox.queue.lock().unwrap_or_else(PoisonError::into_inner).is_empty() {
run_pending_wakes();
}
}
pub fn run_pending_wakes() -> bool {
let inbox = INBOX.with(Arc::clone);
let mut ran = false;
loop {
let ids = inbox.take();
if ids.is_empty() {
return ran;
}
ran = true;
for pending in ids {
match pending {
Pending::Task(id) => poll_task(id),
Pending::Callback(id) => run_callback(id),
}
}
}
}
pub fn block_on<T: Clone + 'static>(future: impl Future<Output = Result<T, ObservableError>> + 'static) -> Result<T, ObservableError> {
let task = spawn_local(future);
let inbox = INBOX.with(Arc::clone);
loop {
run_pending_wakes();
if task.is_completed() {
return task.result();
}
let queue = inbox.queue.lock().unwrap_or_else(PoisonError::into_inner);
if queue.is_empty() {
drop(inbox.arrived.wait(queue).unwrap_or_else(PoisonError::into_inner));
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TaskWorkError {
pub message: String,
}
impl std::fmt::Display for TaskWorkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for TaskWorkError {}
pub fn run<T: Send + 'static>(work: impl FnOnce() -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send + 'static) -> Task<T> {
struct Slot<T> {
outcome: Option<Result<T, String>>,
waker: Option<Waker>,
}
let slot = Arc::new(Mutex::new(Slot { outcome: None, waker: None }));
let worker_slot = slot.clone();
std::thread::spawn(move || {
let outcome = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)) {
Ok(Ok(value)) => Ok(value),
Ok(Err(error)) => Err(error.to_string()),
Err(panic) => Err(panic
.downcast_ref::<&str>()
.map(|text| (*text).to_string())
.or_else(|| panic.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "the work panicked".to_string())),
};
let waker = {
let mut slot = worker_slot.lock().unwrap_or_else(PoisonError::into_inner);
slot.outcome = Some(outcome);
slot.waker.take()
};
if let Some(waker) = waker {
waker.wake();
}
});
spawn_local(std::future::poll_fn(move |context| {
let mut slot = slot.lock().unwrap_or_else(PoisonError::into_inner);
match slot.outcome.take() {
Some(Ok(value)) => Poll::Ready(Ok(value)),
Some(Err(message)) => Poll::Ready(Err(Rc::new(TaskWorkError { message }) as ObservableError)),
None => {
slot.waker = Some(context.waker().clone());
Poll::Pending
}
}
}))
}
pub fn canceled<T>() -> Result<T, ObservableError> {
Err(Rc::new(TaskCanceledError::default()))
}