Capture task early returns and exit returns, with tokio

⚓ Rust    📅 2025-06-26    👤 surdeus    👁️ 6      

surdeus

Warning

This post was published 47 days ago. The information described in this article may have changed.

I'm trying to write an application using Tokio. The idea is to spawn to tasks that return anyhow::Result<>. The tasks should be stopped by a cancellation token if the user hits Ctrl+C.
A task returning early should result in the same behavior as if the user pressed Ctrl+C, with the addition of printing the task return value (A task shutting down early is suspicious, even if it returned Ok()).

At exit, when all tasks have been shut down, I also want to print any tasks returning Err().

The below code is basically what I have so far. Unfortunately, the TaskTracker swallows any errors returned at exit.

How should I structure this? I feel like there should be a standard way.

Thanks!

    let ctrl_c = tokio::signal::ctrl_c();
    let mut sig_term = tokio::signal::unix::signal(SignalKind::terminate())?;

    let cancel_token = CancellationToken::new();
    let tracker = TaskTracker::new();

    let prober: JoinHandle<anyhow::Result<()>> = tracker.spawn(
        prober(cancel_token.clone())
        .instrument(info_span!("watcher_task")),
    );

    let cancel_clone = cancel_token.clone();
    let signal_task = tracker.spawn(
        signal_main(&config.signal, cancel_clone, song_update_rx)
            .instrument(info_span!("signal_task")),
    );

    select! {
        res = prober => {
            error!("Watcher task early exit: {res:?}")
        },
        res = signal_task => {
            error!("Signal task early exit: {res:?}")
        }
        _ = ctrl_c => {
            info!("Received Ctrl+C (SIGINT)");
        },
        _ = sig_term.recv() => {
            info!("Received SIGTERM");
        },
    }
    info!("Waiting for tasks to exit");
    cancel_token.cancel();
    tracker.wait().await;
    // Waiting without using the tracker, results in panic due to having used the futures multiple times
    // if let Err(e) = prober.await? {
    //     error!("Prober exit error: {e}");
    // }
    // if let Err(e) = signal_task.await? {
    //     error!("Signal exit error: {e}");
    // }

1 post - 1 participant

Read full topic

🏷️ rust_feed