use std::future::Future;
use std::sync::Arc;
use std::time::{Duration, Instant};
use cronoq_core::{Snapshot, StatusEvent, Submitted, TaskId, TaskRef, TaskStatus};
use tokio::runtime::{Handle, Runtime};
use tokio::sync::broadcast;
use crate::error::{Error, TaskFailure};
use crate::scheduler::{Builder, Scheduler, TaskHandle};
use crate::task::{Recurring, Task};
fn block_on<F>(rt: &Handle, f: F) -> F::Output
where
F: Future + Send,
F::Output: Send,
{
if Handle::try_current().is_ok() {
std::thread::scope(|s| {
s.spawn(|| rt.block_on(f))
.join()
.unwrap_or_else(|p| std::panic::resume_unwind(p))
})
} else {
rt.block_on(f)
}
}
struct Unpark(std::thread::Thread);
impl std::task::Wake for Unpark {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
}
fn park_until<F: Future + Unpin>(mut f: F, deadline: Option<Instant>) -> Option<F::Output> {
let waker = std::task::Waker::from(Arc::new(Unpark(std::thread::current())));
let mut cx = std::task::Context::from_waker(&waker);
loop {
if let std::task::Poll::Ready(v) = std::pin::Pin::new(&mut f).poll(&mut cx) {
return Some(v);
}
match deadline {
None => std::thread::park(),
Some(d) => {
let now = Instant::now();
if now >= d {
return None;
}
std::thread::park_timeout(d - now);
}
}
}
}
pub struct BlockingScheduler<P, O> {
rt: Option<Runtime>,
inner: Scheduler<P, O>,
}
impl<P: Send + Sync + 'static, O: Send + Sync + 'static> Builder<P, O> {
pub fn build_blocking(self) -> Result<BlockingScheduler<P, O>, Error> {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("cronoq")
.build()
.map_err(|e| Error::Config(format!("runtime: {e}")))?;
let inner = block_on(rt.handle(), self.build())?;
Ok(BlockingScheduler {
rt: Some(rt),
inner,
})
}
}
impl<P: Send + Sync + 'static, O: Send + Sync + 'static> BlockingScheduler<P, O> {
fn handle(&self) -> &Handle {
self.rt.as_ref().expect("runtime alive").handle()
}
fn wrap(&self, h: TaskHandle<O>) -> BlockingHandle<O> {
BlockingHandle { handle: Some(h) }
}
pub fn submit(&self, task: impl Into<Task<P>>) -> Result<BlockingHandle<O>, Error> {
let task = task.into();
let h = block_on(self.handle(), self.inner.submit(task))?;
Ok(self.wrap(h))
}
pub fn enqueue(&self, task: impl Into<Task<P>>) -> Result<Submitted, Error> {
let task = task.into();
block_on(self.handle(), self.inner.enqueue(task))
}
pub fn enqueue_many(
&self,
tasks: Vec<Task<P>>,
) -> Result<Vec<Result<Submitted, Error>>, Error> {
block_on(self.handle(), self.inner.enqueue_many(tasks))
}
pub fn submit_batch(&self, tasks: Vec<Task<P>>) -> Result<Vec<BlockingHandle<O>>, Error> {
let hs = block_on(self.handle(), self.inner.submit_batch(tasks))?;
Ok(hs.into_iter().map(|h| self.wrap(h)).collect())
}
pub fn task_handle(
&self,
target: impl Into<TaskRef>,
) -> Result<Option<BlockingHandle<O>>, Error> {
let target = target.into();
let h = block_on(self.handle(), self.inner.handle(target))?;
Ok(h.map(|h| self.wrap(h)))
}
pub fn cancel(&self, target: impl Into<TaskRef>) -> Result<bool, Error> {
let target = target.into();
block_on(self.handle(), self.inner.cancel(target))
}
pub fn status(&self, target: impl Into<TaskRef>) -> Result<Option<TaskStatus>, Error> {
let target = target.into();
block_on(self.handle(), self.inner.status(target))
}
pub fn snapshot(&self) -> Result<Snapshot, Error> {
block_on(self.handle(), self.inner.snapshot())
}
pub fn set_capacity(&self, pool: &str, capacity: u32) -> Result<(), Error> {
block_on(self.handle(), self.inner.set_capacity(pool, capacity))
}
pub fn add_recurring(&self, r: Recurring<P>) -> Result<(), Error> {
block_on(self.handle(), self.inner.add_recurring(r))
}
pub fn remove_recurring(&self, name: &str) -> Result<bool, Error> {
block_on(self.handle(), self.inner.remove_recurring(name))
}
pub fn events(&self) -> broadcast::Receiver<StatusEvent> {
self.inner.events()
}
pub fn scheduler(&self) -> &Scheduler<P, O> {
&self.inner
}
pub fn shutdown(mut self, grace: Duration) -> Result<(), Error> {
let r = block_on(self.handle(), self.inner.shutdown(grace));
if let Some(rt) = self.rt.take() {
if Handle::try_current().is_ok() {
rt.shutdown_background();
} else {
rt.shutdown_timeout(Duration::from_secs(1));
}
}
r
}
}
impl<P, O> Drop for BlockingScheduler<P, O> {
fn drop(&mut self) {
if let Some(rt) = self.rt.take() {
rt.shutdown_background();
}
}
}
pub struct BlockingHandle<O> {
handle: Option<TaskHandle<O>>,
}
impl<O: Send + Sync + 'static> BlockingHandle<O> {
pub fn id(&self) -> TaskId {
self.handle.as_ref().expect("not yet awaited").id()
}
pub fn cancel(&self) {
if let Some(h) = &self.handle {
h.cancel();
}
}
pub fn wait(mut self) -> Result<Arc<O>, TaskFailure> {
let h = self.handle.take().expect("not yet awaited");
park_until(h, None).expect("no deadline")
}
pub fn wait_timeout(&mut self, timeout: Duration) -> Option<Result<Arc<O>, TaskFailure>> {
let h = self.handle.as_mut()?;
let r = park_until(h, Some(Instant::now() + timeout));
if r.is_some() {
self.handle = None;
}
r
}
}