Hello all
I'd like to define a trait for a process which starts (and continues to run asynchronously), and can later be stopped - an example below:
use std::future::Future;
use std::pin::Pin;
use tokio_util::sync::CancellationToken;
use async_trait::async_trait;
#[tokio::main]
pub async fn main() {
#[async_trait]
trait Process {
async fn start(&mut self);
fn log(&self, msg: &str);
async fn stop(&mut self);
}
struct Processor {
running_future: Option<(CancellationToken, Pin<Box<dyn Future<Output = ()> + Send>>)>,
}
#[async_trait]
impl Process for Processor {
async fn start(&mut self) {
let cancellation_token = CancellationToken::new();
let cancellation_token_ = cancellation_token.clone();
let future = async move {
for x in 0..20 {
if cancellation_token_.is_cancelled() {
println!("Process cancelled!");
break;
};
println!("x: {}", x);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
};
let future = Box::pin(future);
self.running_future = Some((cancellation_token, future));
}
fn log(&self, msg: &str) {
println!("logging: {}", msg);
}
async fn stop(&mut self) {
if let Some((cancellation_token, future)) = Option::take(&mut self.running_future) {
cancellation_token.cancel();
future.await;
self.running_future = None;
}
}
}
let mut processor = Processor { running_future: None };
println!("Starting processor");
processor.start().await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
processor.log("logging!");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
processor.stop().await;
}
The issue with this is that the process (within the future
variable) never does anything, because it is not .await
-ed until stop()
is called, at which point the cancellation token has already been cancelled. If I await the future within start()
, then obviously it never gets to the stop()
call at all.
Is the trait itself a bad design pattern? Or is there some idiomatic way to make the future start during start()
, without having to await
it?
Thanks for any help!