watchexec_supervisor/job/
state.rs

1use std::{sync::Arc, time::Instant};
2
3#[cfg(not(test))]
4use process_wrap::tokio::TokioChildWrapper;
5use process_wrap::tokio::TokioCommandWrap;
6use tracing::trace;
7use watchexec_events::ProcessEnd;
8
9use crate::command::Command;
10
11/// The state of the job's command / process.
12///
13/// This is used both internally to represent the current state (ready/pending, running, finished)
14/// of the command, and can be queried via the [`JobTaskContext`](super::JobTaskContext) by hooks.
15///
16/// Technically, some operations can be done through a `&self` shared borrow on the running
17/// command's [`TokioChildWrapper`], but this library recommends against taking advantage of this,
18/// and prefer using the methods on [`Job`](super::Job) instead, so that the job can keep track of
19/// what's going on.
20#[derive(Debug)]
21#[cfg_attr(test, derive(Clone))]
22pub enum CommandState {
23	/// The command is neither running nor has finished. This is the initial state.
24	Pending,
25
26	/// The command is currently running. Note that this is established after the process is spawned
27	/// and not precisely synchronised with the process' aliveness: in some cases the process may be
28	/// exited but still `Running` in this enum.
29	Running {
30		/// The child process (test version).
31		#[cfg(test)]
32		child: super::TestChild,
33
34		/// The child process.
35		#[cfg(not(test))]
36		child: Box<dyn TokioChildWrapper>,
37
38		/// The time at which the process was spawned.
39		started: Instant,
40	},
41
42	/// The command has completed and its status was collected.
43	Finished {
44		/// The command's exit status.
45		status: ProcessEnd,
46
47		/// The time at which the process was spawned.
48		started: Instant,
49
50		/// The time at which the process finished, or more precisely, when its status was collected.
51		finished: Instant,
52	},
53}
54
55impl CommandState {
56	/// Whether the command is pending, i.e. not running or finished.
57	#[must_use]
58	pub const fn is_pending(&self) -> bool {
59		matches!(self, Self::Pending)
60	}
61
62	/// Whether the command is running.
63	#[must_use]
64	pub const fn is_running(&self) -> bool {
65		matches!(self, Self::Running { .. })
66	}
67
68	/// Whether the command is finished.
69	#[must_use]
70	pub const fn is_finished(&self) -> bool {
71		matches!(self, Self::Finished { .. })
72	}
73
74	#[cfg_attr(test, allow(unused_mut, unused_variables))]
75	pub(crate) fn spawn(
76		&mut self,
77		command: Arc<Command>,
78		mut spawnable: TokioCommandWrap,
79	) -> std::io::Result<bool> {
80		if let Self::Running { .. } = self {
81			trace!("command running, not spawning again");
82			return Ok(false);
83		}
84
85		trace!(?command, "spawning command");
86
87		#[cfg(test)]
88		let child = super::TestChild::new(command)?;
89
90		#[cfg(not(test))]
91		let child = spawnable.spawn()?;
92
93		*self = Self::Running {
94			child,
95			started: Instant::now(),
96		};
97		Ok(true)
98	}
99
100	#[must_use]
101	pub(crate) fn reset(&mut self) -> Self {
102		trace!(?self, "resetting command state");
103		match self {
104			Self::Pending => Self::Pending,
105			Self::Finished {
106				status,
107				started,
108				finished,
109				..
110			} => {
111				let copy = Self::Finished {
112					status: *status,
113					started: *started,
114					finished: *finished,
115				};
116
117				*self = Self::Pending;
118				copy
119			}
120			Self::Running { started, .. } => {
121				let copy = Self::Finished {
122					status: ProcessEnd::Continued,
123					started: *started,
124					finished: Instant::now(),
125				};
126
127				*self = Self::Pending;
128				copy
129			}
130		}
131	}
132
133	pub(crate) async fn wait(&mut self) -> std::io::Result<bool> {
134		if let Self::Running { child, started } = self {
135			let end = Box::into_pin(child.wait()).await?;
136			*self = Self::Finished {
137				status: end.into(),
138				started: *started,
139				finished: Instant::now(),
140			};
141			Ok(true)
142		} else {
143			Ok(false)
144		}
145	}
146}