Tokio: try_join! with an optional task

Is there a way to use tokio::try_join! with a variable number of tasks?

Background:
I have a server that runs a few tasks to monitor some backend resources. It spawns a task for each one, then runs tokio::try_join! on all the tasks (The tasks are continuous loops, so they aren't expected to return). I'd like to add a new task to monitor a new resource; but I would like to be able to run the server even if the resource is unavailable. So I'd like to have an optional task that can be added to the try_join! call if it exists (in my mind, this would be an Option<JoinHandle<...>> but I'm not committed to that).

Essentially, what I'm looking at is something like this:

use std::convert::Infallible;

async fn repeat(i: u64) -> Result<(), Infallible> {
    loop {
        println!("{}: going to sleep for {} seconds", i, i);
        tokio::time::sleep(std::time::Duration::from_secs(i)).await;
    }
    
    Ok(())
}

#[tokio::main]
async fn main() {
    let t1 = tokio::task::spawn(repeat(2));
    let t2 = tokio::task::spawn(repeat(3));

    let enabled = true;
    let t3 = if enabled {
        Some(tokio::task::spawn(repeat(1)))
    } else {
        None
    };
    // What to do with t3?
    
    tokio::try_join!(t1, t2);
}

I suppose if I wanted, I could have the false case for t3 be a task that immediately returns Ok(()); but I was wondering if there was a more straightforward way of doing this, or if I should rethink what I'm doing entirely.

You can replace branch with something that returns immediately like future::ok(()) and then unify both branches to a single type, by either boxing, or better by using FutureExt::{left|right}_future so it might look something like this

use futures::FutureExt;

async fn something() -> Result<(), Error> {
   Ok(())
}

#[tokio::main]
fn main() -> Result<(), Error> {
   let maybe_enabled = if enabled {
      something().left_future()
   } else {
      future::ok(()).right_future()
   };
   try_join!(maybe_enabled, ...).await?;
}

You can also do it with an async block:

    let join_t3 = async move {
        if let Some(t3) = t3 {
            t3.await
        } else {
            Ok(Ok(()))
        }
    };
    tokio::try_join!(t1, t2, join_t3);

Note that awaiting JoinHandles in your example will return Result<Result<(), Infallible>, JoinError>. You should probably flatten the result before using it in try_join to handle errors on both levels.

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.