Skip to main content

soil_cli/
signals.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use futures::{
8	future::{self, BoxFuture, FutureExt},
9	pin_mut, select, Future,
10};
11
12use soil_service::Error as ServiceError;
13
14/// Abstraction over OS signals to handle the shutdown of the node smoothly.
15///
16/// On `unix` this represents `SigInt` and `SigTerm`.
17pub struct Signals(BoxFuture<'static, ()>);
18
19impl Signals {
20	/// Return the signals future.
21	pub fn future(self) -> BoxFuture<'static, ()> {
22		self.0
23	}
24
25	/// Capture the relevant signals to handle shutdown of the node smoothly.
26	///
27	/// Needs to be called in a Tokio context to have access to the tokio reactor.
28	#[cfg(target_family = "unix")]
29	pub fn capture() -> std::result::Result<Self, ServiceError> {
30		use tokio::signal::unix::{signal, SignalKind};
31
32		let mut stream_int = signal(SignalKind::interrupt()).map_err(ServiceError::Io)?;
33		let mut stream_term = signal(SignalKind::terminate()).map_err(ServiceError::Io)?;
34
35		Ok(Signals(
36			async move {
37				future::select(stream_int.recv().boxed(), stream_term.recv().boxed()).await;
38			}
39			.boxed(),
40		))
41	}
42
43	/// Capture the relevant signals to handle shutdown of the node smoothly.
44	///
45	/// Needs to be called in a Tokio context to have access to the tokio reactor.
46	#[cfg(not(unix))]
47	pub fn capture() -> Result<Self, ServiceError> {
48		use tokio::signal::ctrl_c;
49
50		Ok(Signals(
51			async move {
52				let _ = ctrl_c().await;
53			}
54			.boxed(),
55		))
56	}
57
58	/// A dummy signal that never returns.
59	pub fn dummy() -> Self {
60		Self(future::pending().boxed())
61	}
62
63	/// Run a future task until receive a signal.
64	pub async fn run_until_signal<F, E>(self, func: F) -> Result<(), E>
65	where
66		F: Future<Output = Result<(), E>> + future::FusedFuture,
67		E: std::error::Error + Send + Sync + 'static,
68	{
69		let signals = self.future().fuse();
70
71		pin_mut!(func, signals);
72
73		select! {
74			_ = signals => {},
75			res = func => res?,
76		}
77
78		Ok(())
79	}
80
81	/// Execute the future task and returns it's value if it completes before the signal.
82	pub async fn try_until_signal<F, T>(self, func: F) -> Result<T, ()>
83	where
84		F: Future<Output = T> + future::FusedFuture,
85	{
86		let signals = self.future().fuse();
87
88		pin_mut!(func, signals);
89
90		select! {
91			s = signals => Err(s),
92			res = func => Ok(res),
93		}
94	}
95}