mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-07 20:08:02 +00:00
bc53b9a03a
* Change copyright year to 2023 from 2022 * Fix incorrect update of copyright year * Remove years from copy right header * Fix remaining files * Fix typo in a header and remove update-copyright.sh
141 lines
4.1 KiB
Rust
141 lines
4.1 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use std::{
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc,
|
|
},
|
|
time::Duration,
|
|
};
|
|
|
|
/// An error signifying that a task has been cancelled due to a timeout.
|
|
#[derive(Debug)]
|
|
pub struct Timeout;
|
|
|
|
impl std::error::Error for Timeout {}
|
|
impl std::fmt::Display for Timeout {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
fmt.write_str("task has been running too long")
|
|
}
|
|
}
|
|
|
|
/// A handle which can be used to check whether the task has been cancelled due to a timeout.
|
|
#[repr(transparent)]
|
|
pub struct IsTimedOut(Arc<AtomicBool>);
|
|
|
|
impl IsTimedOut {
|
|
#[must_use]
|
|
pub fn check_if_timed_out(&self) -> std::result::Result<(), Timeout> {
|
|
if self.0.load(Ordering::Relaxed) {
|
|
Err(Timeout)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error for a task which either panicked, or has been cancelled due to a timeout.
|
|
#[derive(Debug)]
|
|
pub enum SpawnWithTimeoutError {
|
|
JoinError(tokio::task::JoinError),
|
|
Timeout,
|
|
}
|
|
|
|
impl std::error::Error for SpawnWithTimeoutError {}
|
|
impl std::fmt::Display for SpawnWithTimeoutError {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
SpawnWithTimeoutError::JoinError(error) => error.fmt(fmt),
|
|
SpawnWithTimeoutError::Timeout => Timeout.fmt(fmt),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CancelOnDrop(Arc<AtomicBool>);
|
|
impl Drop for CancelOnDrop {
|
|
fn drop(&mut self) {
|
|
self.0.store(true, Ordering::Relaxed)
|
|
}
|
|
}
|
|
|
|
/// Spawns a new blocking task with a given `timeout`.
|
|
///
|
|
/// The `callback` should continuously call [`IsTimedOut::check_if_timed_out`],
|
|
/// which will return an error once the task runs for longer than `timeout`.
|
|
///
|
|
/// If `timeout` is `None` then this works just as a regular `spawn_blocking`.
|
|
pub async fn spawn_blocking_with_timeout<R>(
|
|
timeout: Option<Duration>,
|
|
callback: impl FnOnce(IsTimedOut) -> std::result::Result<R, Timeout> + Send + 'static,
|
|
) -> Result<R, SpawnWithTimeoutError>
|
|
where
|
|
R: Send + 'static,
|
|
{
|
|
let is_timed_out_arc = Arc::new(AtomicBool::new(false));
|
|
let is_timed_out = IsTimedOut(is_timed_out_arc.clone());
|
|
let _cancel_on_drop = CancelOnDrop(is_timed_out_arc);
|
|
let task = tokio::task::spawn_blocking(move || callback(is_timed_out));
|
|
|
|
let result = if let Some(timeout) = timeout {
|
|
tokio::select! {
|
|
// Shouldn't really matter, but make sure the task is polled before the timeout,
|
|
// in case the task finishes after the timeout and the timeout is really short.
|
|
biased;
|
|
|
|
task_result = task => task_result,
|
|
_ = tokio::time::sleep(timeout) => Ok(Err(Timeout))
|
|
}
|
|
} else {
|
|
task.await
|
|
};
|
|
|
|
match result {
|
|
Ok(Ok(result)) => Ok(result),
|
|
Ok(Err(Timeout)) => Err(SpawnWithTimeoutError::Timeout),
|
|
Err(error) => Err(SpawnWithTimeoutError::JoinError(error)),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn spawn_blocking_with_timeout_works() {
|
|
let task: Result<(), SpawnWithTimeoutError> =
|
|
spawn_blocking_with_timeout(Some(Duration::from_millis(100)), |is_timed_out| {
|
|
std::thread::sleep(Duration::from_millis(200));
|
|
is_timed_out.check_if_timed_out()?;
|
|
unreachable!();
|
|
})
|
|
.await;
|
|
|
|
assert_matches::assert_matches!(task, Err(SpawnWithTimeoutError::Timeout));
|
|
|
|
let task = spawn_blocking_with_timeout(Some(Duration::from_millis(100)), |is_timed_out| {
|
|
std::thread::sleep(Duration::from_millis(20));
|
|
is_timed_out.check_if_timed_out()?;
|
|
Ok(())
|
|
})
|
|
.await;
|
|
|
|
assert_matches::assert_matches!(task, Ok(()));
|
|
}
|
|
}
|