Tokio panics during shutdown

I have the following base-structure to an async tokio app, where multiple jobs/services are running asynchronously:

use tokio::{runtime::Runtime, signal};
use futures::future;
use tokio_schedule::{every, Job};

fn main() {
    let runtime = Runtime::new().unwrap();
    let handle = runtime.handle().clone();

    handle.spawn(async move {
        let _ = signal::ctrl_c().await;

        runtime.shutdown_background();
    });

    let job1 = every(3).seconds().perform(|| async {
        // do some work
        println!("3s scheduled execution");
    });

    let job2 = every(2).seconds().perform(|| async {
        // do some work
        println!("2s scheduled execution");
    });

    handle.block_on(future::join_all([job1, job2]));
}

When I shut this application down, I get a panic A Tokio 1.x context was found, but it is being shutdown. from one of the scheduled tasks. This is a recent refactoring from using task::spawn where cleanup of the tasks was not performed correctly.

Any idea how this panic can be avoided?

If the runtime has been shut down

If the Handle’s associated Runtime has been shut down (through Runtime::shutdown_background, Runtime::shutdown_timeout, or by dropping it) and Handle::block_on is used it might return an error or panic. Specifically IO resources will return an error and timers will panic. Runtime independent futures will run as normal.

src: Handle in tokio::runtime - Rust

A solution can be

use tokio::{runtime::Runtime, signal};
use tokio_schedule::{every, Job};

fn main() {
    let runtime = Runtime::new().unwrap();

    let job1 = every(3).seconds().perform(|| async {
        // do some work
        println!("3s scheduled execution");
    });

    let job2 = every(2).seconds().perform(|| async {
        // do some work
        println!("2s scheduled execution");
    });

    let ctrl_c_handle = async move {
        let _ = signal::ctrl_c().await;
    };

    let ctrl_c = runtime.block_on(async {
        tokio::select! {
            _ = job1 => false,
            _ = job2 => false,
            _ = ctrl_c_handle => true,
        }
    });
    if ctrl_c {
        println!("Program exit due to ctrl_c!")
    }
}
2 Likes

Thanks for the suggestion, this appears to solve most of my problems with dropped resources.

But is there a way to do the select on a dynamic list (Vec) of Futures?

You might want to look into FuturesUnordered.

4 Likes

Thx vague and Cerber-Ursi, the reference to FuturesUnordered was the last puzzle piece I needed.

This example now:

  • shuts down cleanly
  • drops resources
  • does not panic
use std::time::Duration;

use futures::{stream::FuturesUnordered, FutureExt, StreamExt};
use tokio::runtime::Runtime;

struct SomeResource(String);

impl SomeResource {
    fn print_message(&self) {
        println!("{}", self.0);
    }
}

impl Drop for SomeResource {
    fn drop(&mut self) {
        println!("Some resource has been dropped");
    }
}

fn main() {
    let runtime = Runtime::new().unwrap();

    let mut fs = FuturesUnordered::new();

    let shutdown_trigger = async {
        let _ = tokio::signal::ctrl_c().await;
    };
    fs.push(shutdown_trigger.boxed());
    fs.push(async_loop(3).boxed());
    fs.push(async_loop(2).boxed());

    runtime.block_on(async move {
        if let Some(_) = fs.next().await {
            // extract information about shutdown
        }
    });
}

async fn async_loop(loop_secs: u32) {
    let resource = SomeResource(format!("{loop_secs}s scheduled execution"));
    loop {
        resource.print_message();
        tokio::time::sleep(Duration::from_secs(2)).await;
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.