Cannot make multiple async functions work together

I am trying to use multiple async functions in a single async function and wait for all the executions before return and finally exit.

My understanding is not enough to make the compiler happy.

The textbook example works.

use futures::join;

use lambda_http::{run, service_fn, Body, Error, Request, Response};

async fn one() -> i32 {
    1
}
async fn two() -> i32 {
    2
}

async fn function_handler(_event: Request) -> Result<Response<Body>, Error> {
    let (_one, _two) = join!(one(), two());

    let resp = Response::builder()
        .status(200)
        .header("content-type", "application/json")
        .body("Hello AWS Lambda HTTP request".into())
        .map_err(Box::new)?;
    Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_target(false)
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}

What does not work is:

One of the async functions that I would like join later on:

pub async fn create_db_eid_cache(
    aws_clients: AwsClients,
    config: &Config,
) -> Result<String, Box<dyn error::Error>> {
    let db_names = get_all_database_names(aws_clients.glue_client).await?;
    let db_eid_cache_opt = create_eid_cache(db_names);
    if db_eid_cache_opt.is_none() {
        return Ok("There is no database in this catalog".to_string());
    }
    let db_eid_cache = db_eid_cache_opt.unwrap();
    let db_eid_cache_json = serde_json::to_string(&db_eid_cache)?;
    let s3_path_prefix = get_cache_key("db-eid-cache");

    match s3_put_object_bytes(
        aws_clients.s3_client,
        &config.target_s3_bucket,
        &s3_path_prefix,
        &db_eid_cache_json,
    )
    .await
    {
        Ok(_v) => Ok("Successfully updated cache".to_string()),
        Err(e) => Err(Box::from(e)),
    }
}

The error:

cache_manager
async fn function_handler(_event: Request) -> Result<Response<Body>, Error>
Go to Future | Sized

future cannot be sent between threads safely
the trait `std::marker::Send` is not implemented for `(dyn StdError + 'static)`rustcClick for full compiler diagnostic
join_mod.rs(95, 13): future is not `Send` as this value is used across an await
join_mod.rs(97, 13): the value is later dropped here
lib.rs(185, 16): required by a bound in `lambda_http::run`

This is the official solution:

https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html

I am not sure how to do this in the context of my function.

Change Box<dyn Error> to Box<dyn Error + Send + Sync> (or use anyhow)

Next time you post a question, please post the full error as cargo build prints it.

1 Like

Thanks @alice,

I have tried this as well before.

Here is the next trouble:

pub async fn create_db_eid_cache(
    aws_clients: AwsClients,
    config: &Config,
) -> Result<String, Box<dyn error::Error + Send + Sync>> {
    let db_names = get_all_database_names(aws_clients.glue_client).await?;
    let db_eid_cache_opt = create_eid_cache(db_names);
    if db_eid_cache_opt.is_none() {
        return Ok("There is no database in this catalog".to_string());
    }
    let db_eid_cache = db_eid_cache_opt.unwrap();
    let db_eid_cache_json = serde_json::to_string(&db_eid_cache)?;
    let s3_path_prefix = get_cache_key("db-eid-cache");

    match s3_put_object_bytes(
        aws_clients.s3_client,
        &config.target_s3_bucket,
        &s3_path_prefix,
        &db_eid_cache_json,
    )
    .await
    {
        Ok(_v) => Ok("Successfully updated cache".to_string()),
        Err(e) => Err(Box::from(e)),
    }
}

Error:

error[E0277]: the size for values of type `dyn StdError` cannot be known at compilation time
  --> src/db_eid_cache.rs:10:73
   |
10 |     let db_names = get_all_database_names(aws_clients.glue_client).await?;
   |                                                                         ^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `dyn StdError`
   = help: the following other types implement trait `FromResidual<R>`:
             <Result<T, F> as FromResidual<Result<Infallible, E>>>
             <Result<T, F> as FromResidual<Yeet<E>>>
   = note: required for `Box<dyn StdError>` to implement `StdError`
   = note: required for `Box<dyn StdError + Sync + std::marker::Send>` to implement `From<Box<dyn StdError>>`
   = note: required for `Result<std::string::String, Box<dyn StdError + Sync + std::marker::Send>>` to implement `FromResidual<Result<Infallible, Box<dyn StdError>>>`

error[E0277]: `dyn StdError` cannot be sent between threads safely
  --> src/db_eid_cache.rs:10:73
   |
10 |     let db_names = get_all_database_names(aws_clients.glue_client).await?;
   |                                                                         ^ `dyn StdError` cannot be sent between threads safely

What is the best approach for creating a async function that has potentially 5 different Ok Err cases? The real question if the Err handling part. I have seen many different ways but not sure what would be the best.

One suggestion was to use Result<String, Box> so that all different Err cases get converted to this.

However, it breaks down when I try to combine such functions with join! and I try to add Send + Sync to such functions.

Should I create a wrapper struct that has all cases (for example Result<String, Error> from serde_json::to_string and Result<GetDatabasesOutput, SdkError<GetDatabasesError, Response>> from glue_client.get_databases()) ?

As far as my understanding goes Send and Sync are traits (from here https://doc.rust-lang.org/nomicon/send-and-sync.html) but I am not sure what to do in cases when these are not implemented.

Thanks for considering answering this.

You should change it to Box<dyn Error + Send + Sync> everywhere. It doesn't work if you just do it sometimes.

Your error is completely unrelated to join!. It's because you're using a Box<dyn Error>. It would also happen in situations that don't involve join!.

When the error originates from a dyn Trait type, the solution is to add + Send and sometimes also + Sync.

(Or if you're defining the trait yourself, you can alternatively add them to the definition of the trait.)

Thanks @alice this works.

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.