watchexec/crates/supervisor/src/job/messages.rs

149 lines
4.8 KiB
Rust

use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use futures::{future::select, FutureExt};
use watchexec_signals::Signal;
use crate::flag::Flag;
use super::task::{
AsyncErrorHandler, AsyncFunc, AsyncSpawnHook, SyncErrorHandler, SyncFunc, SyncSpawnHook,
};
/// The underlying control message types for [`Job`](super::Job).
///
/// You may use [`Job::control()`](super::Job::control()) to send these messages directly, but in
/// general should prefer the higher-level methods on [`Job`](super::Job) itself.
pub enum Control {
/// For [`Job::start()`](super::Job::start()).
Start,
/// For [`Job::stop()`](super::Job::stop()).
Stop,
/// For [`Job::stop_with_signal()`](super::Job::stop_with_signal()).
GracefulStop {
/// Signal to send immediately
signal: Signal,
/// Time to wait before forceful termination
grace: Duration,
},
/// For [`Job::try_restart()`](super::Job::try_restart()).
TryRestart,
/// For [`Job::try_restart_with_signal()`](super::Job::try_restart_with_signal()).
TryGracefulRestart {
/// Signal to send immediately
signal: Signal,
/// Time to wait before forceful termination and restart
grace: Duration,
},
/// Internal implementation detail of [`Control::TryGracefulRestart`].
ContinueTryGracefulRestart,
/// For [`Job::signal()`](super::Job::signal()).
Signal(Signal),
/// For [`Job::delete()`](super::Job::delete()) and [`Job::delete_now()`](super::Job::delete_now()).
Delete,
/// For [`Job::to_wait()`](super::Job::to_wait()).
NextEnding,
/// For [`Job::run()`](super::Job::run()).
SyncFunc(SyncFunc),
/// For [`Job::run_async()`](super::Job::run_async()).
AsyncFunc(AsyncFunc),
/// For [`Job::set_spawn_hook()`](super::Job::set_spawn_hook()).
SetSyncSpawnHook(SyncSpawnHook),
/// For [`Job::set_spawn_async_hook()`](super::Job::set_spawn_async_hook()).
SetAsyncSpawnHook(AsyncSpawnHook),
/// For [`Job::unset_spawn_hook()`](super::Job::unset_spawn_hook()).
UnsetSpawnHook,
/// For [`Job::set_error_handler()`](super::Job::set_error_handler()).
SetSyncErrorHandler(SyncErrorHandler),
/// For [`Job::set_async_error_handler()`](super::Job::set_async_error_handler()).
SetAsyncErrorHandler(AsyncErrorHandler),
/// For [`Job::unset_error_handler()`](super::Job::unset_error_handler()).
UnsetErrorHandler,
}
impl std::fmt::Debug for Control {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Start => f.debug_struct("Start").finish(),
Self::Stop => f.debug_struct("Stop").finish(),
Self::GracefulStop { signal, grace } => f
.debug_struct("GracefulStop")
.field("signal", signal)
.field("grace", grace)
.finish(),
Self::TryRestart => f.debug_struct("TryRestart").finish(),
Self::TryGracefulRestart { signal, grace } => f
.debug_struct("TryGracefulRestart")
.field("signal", signal)
.field("grace", grace)
.finish(),
Self::ContinueTryGracefulRestart => {
f.debug_struct("ContinueTryGracefulRestart").finish()
}
Self::Signal(signal) => f.debug_struct("Signal").field("signal", signal).finish(),
Self::Delete => f.debug_struct("Delete").finish(),
Self::NextEnding => f.debug_struct("NextEnding").finish(),
Self::SyncFunc(_) => f.debug_struct("SyncFunc").finish_non_exhaustive(),
Self::AsyncFunc(_) => f.debug_struct("AsyncFunc").finish_non_exhaustive(),
Self::SetSyncSpawnHook(_) => f.debug_struct("SetSyncSpawnHook").finish_non_exhaustive(),
Self::SetAsyncSpawnHook(_) => {
f.debug_struct("SetSpawnAsyncHook").finish_non_exhaustive()
}
Self::UnsetSpawnHook => f.debug_struct("UnsetSpawnHook").finish(),
Self::SetSyncErrorHandler(_) => f
.debug_struct("SetSyncErrorHandler")
.finish_non_exhaustive(),
Self::SetAsyncErrorHandler(_) => f
.debug_struct("SetAsyncErrorHandler")
.finish_non_exhaustive(),
Self::UnsetErrorHandler => f.debug_struct("UnsetErrorHandler").finish(),
}
}
}
#[derive(Debug)]
pub(crate) struct ControlMessage {
pub control: Control,
pub done: Flag,
}
/// Lightweight future which resolves when the corresponding control has been run.
///
/// Unlike most futures, tickets don't need to be polled for controls to make progress; the future
/// is only used to signal completion. Dropping a ticket will not drop the control, so it's safe to
/// do so if you don't care about when the control completes.
///
/// Tickets can be cloned, and all clones will resolve at the same time.
#[derive(Debug, Clone)]
pub struct Ticket {
pub(crate) job_gone: Flag,
pub(crate) control_done: Flag,
}
impl Ticket {
pub(crate) fn cancelled() -> Self {
Self {
job_gone: Flag::new(true),
control_done: Flag::new(true),
}
}
}
impl Future for Ticket {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut select(self.job_gone.clone(), self.control_done.clone()).map(|_| ())).poll(cx)
}
}